版本: v1.0 | 日期: 2026-03-26
問題: 使用者需要的資料超出現有 LibreNMS/Graylog Tools 時,如何處理?
| # | 方案 | 開發成本 | 使用者門檻 | 彈性 | 安全性 | 推薦場景 |
|---|---|---|---|---|---|---|
| 1 | Generic HTTP Tool | 零 | 低 | 高 | 中 | 任何有 REST API 的系統 |
| 2 | SSH Command Tool | 零 | 中 | 高 | 低 | On-prem 伺服器指令 |
| 3 | MCP Server 動態掛載 | 低 | 低 | 最高 | 高 | 長期擴展、生態整合 |
| 4 | Agent 自寫程式碼 | 零 | 零 | 高 | 最低 | 一次性分析、快速驗證 |
| 5 | Low-Code Connector Builder | 中 | 最低 | 中 | 高 | 非工程師自助建立 |
| 6 | Plugin 目錄熱載入 | 中 | 中 | 高 | 高 | 開發團隊標準化擴展 |
內建一個萬用 HTTP 呼叫 Tool,Agent 可以打任何 REST API。
// src/tools/generic-http.ts
export const http_request = tool(
async ({ method, url, headers, body, jsonPath }) => {
const res = await fetch(url, {
method,
headers: { "Content-Type": "application/json", ...headers },
body: body ? JSON.stringify(body) : undefined,
});
const data = await res.json();
// 可選:用 jsonPath 提取特定欄位
if (jsonPath) {
return JSON.stringify(extractJsonPath(data, jsonPath));
}
return JSON.stringify(data);
},
{
name: "http_request",
description: "呼叫任意 REST API。用於現有 Tool 不支援的資料源。",
schema: z.object({
method: z.enum(["GET", "POST", "PUT", "DELETE"]).default("GET"),
url: z.string().describe("完整 API URL"),
headers: z.record(z.string()).optional().describe("HTTP headers(如認證)"),
body: z.any().optional().describe("Request body(POST/PUT 用)"),
jsonPath: z.string().optional().describe("從回應中提取的 JSON path,如 'data.items'"),
}),
}
);
使用情境:
使用者:「查一下 vCenter 有哪些 VM」
Agent:目前沒有 vCenter 專用 Tool,但我可以直接呼叫 vCenter REST API。
請提供:
1. vCenter IP 和 port
2. 認證方式(帳號密碼或 Token)
使用者:「IP 是 10.20.92.50,帳號 admin,密碼 xxx」
Agent:[http_request({
method: "POST",
url: "https://10.20.92.50/api/session",
headers: { "Content-Type": "application/json" },
body: { username: "admin", password: "xxx" }
})]
→ 取得 session token
[http_request({
method: "GET",
url: "https://10.20.92.50/api/vcenter/vm",
headers: { "vmware-api-session-id": "<token>" },
jsonPath: "value"
})]
→ 取得 VM 清單
找到 15 台 VM:
| VM 名稱 | 狀態 | CPU | 記憶體 |
|---------|------|-----|--------|
| ... | ... | ... | ... |
優點: 零開發,Agent 自己組裝 API 呼叫
缺點: 每次都要 Agent 重新推理 API 格式;認證資訊在對話中傳遞(安全風險)
改進: 搭配 Secrets Manager 存放認證資訊
內建一個可以 SSH 到指定主機執行命令的 Tool。適合沒有 API 的系統。
// src/tools/ssh-command.ts
import { NodeSSH } from "node-ssh";
export const ssh_command = tool(
async ({ host, command, username, keyPath }) => {
const ssh = new NodeSSH();
await ssh.connect({
host,
username: username || process.env.SSH_DEFAULT_USER,
privateKeyPath: keyPath || process.env.SSH_KEY_PATH,
});
const result = await ssh.execCommand(command);
ssh.dispose();
return JSON.stringify({
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.code,
});
},
{
name: "ssh_command",
description: "SSH 到指定主機執行命令。用於沒有 API 的系統(需要 SSH Key 預先設定)。",
schema: z.object({
host: z.string().describe("目標主機 IP"),
command: z.string().describe("要執行的 shell 指令"),
username: z.string().optional(),
keyPath: z.string().optional(),
}),
}
);
使用情境:
使用者:「查一下備份系統昨天有沒有成功」
Agent:目前沒有備份系統的 Tool,但我可以 SSH 查看。
備份伺服器的 IP 是?
使用者:「10.20.92.100」
Agent:[ssh_command({
host: "10.20.92.100",
command: "tail -20 /var/log/backup/backup.log | grep -E 'SUCCESS|FAIL'"
})]
昨日備份結果:
✅ 2026-03-25 02:00 — Full Backup — SUCCESS (耗時 2h 15m)
✅ 2026-03-25 14:00 — Incremental — SUCCESS (耗時 12m)
優點: 幾乎任何 Linux 系統都能查
缺點: 安全風險最高(Agent 可執行任意指令);需要 SSH Key 預設定
必要限制:
// 安全限制:白名單 + 只讀指令
const ALLOWED_HOSTS = ["10.20.92.100", "10.20.92.200", "10.20.92.201"];
const BLOCKED_COMMANDS = /rm\s|dd\s|mkfs|shutdown|reboot|passwd|chmod\s777/;
if (!ALLOWED_HOSTS.includes(host)) throw new Error("Host not in whitelist");
if (BLOCKED_COMMANDS.test(command)) throw new Error("Destructive command blocked");
使用 Anthropic 的 Model Context Protocol (MCP),每個資料源是一個獨立的 MCP Server,Agent 動態掛載和呼叫。
Agent (MCP Client)
├── MCP Server: LibreNMS → devices, health, alerts, ports
├── MCP Server: Graylog → search, stats, events
├── MCP Server: vCenter → vms, hosts, datastores ← 新增!
├── MCP Server: Backup → jobs, status, schedules ← 新增!
└── MCP Server: Fortinet → vpn, policies, sessions ← 新增!
// mcp-servers/vcenter/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
const server = new McpServer({ name: "vcenter", version: "1.0.0" });
server.tool("list_vms", { description: "列出 vCenter 所有 VM" },
z.object({ datacenter: z.string().optional() }),
async ({ datacenter }) => {
const session = await vcenterLogin();
const vms = await vcenterApi("/api/vcenter/vm", session);
return { content: [{ type: "text", text: JSON.stringify(vms) }] };
}
);
server.tool("get_vm_metrics", { description: "查詢 VM 的 CPU/Memory 使用率" },
z.object({ vmId: z.string() }),
async ({ vmId }) => {
const metrics = await vcenterApi(`/api/vcenter/vm/${vmId}`, session);
return { content: [{ type: "text", text: JSON.stringify(metrics) }] };
}
);
// 啟動 MCP Server(stdio 或 SSE transport)
server.listen({ transport: "stdio" });
// src/mcp/loader.ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
// 從設定檔動態載入 MCP Servers
const mcpConfig = yaml.parse(readFileSync("./mcp-config.yaml", "utf-8"));
for (const server of mcpConfig.servers) {
const transport = new StdioClientTransport({
command: server.command, // e.g., "node"
args: server.args, // e.g., ["./mcp-servers/vcenter/index.js"]
env: server.env, // e.g., { VCENTER_URL: "...", VCENTER_TOKEN: "..." }
});
const client = new Client({ name: `mcp-${server.name}` });
await client.connect(transport);
// 取得該 MCP Server 提供的所有 Tools
const tools = await client.listTools();
// 註冊到 Agent 的 Tool Registry
tools.forEach(t => toolRegistry.register(t));
}
# mcp-config.yaml — 動態掛載設定
servers:
- name: librenms
command: node
args: ["./mcp-servers/librenms/index.js"]
env:
LIBRENMS_URL: "http://100.98.129.83/api/v0"
LIBRENMS_TOKEN: "${LIBRENMS_TOKEN}"
- name: graylog
command: node
args: ["./mcp-servers/graylog/index.js"]
env:
GRAYLOG_URL: "http://100.98.20.188:9000/api"
GRAYLOG_TOKEN: "${GRAYLOG_TOKEN}"
# 新增資料源只需要加一段設定 + 放入 MCP Server
- name: vcenter
command: node
args: ["./mcp-servers/vcenter/index.js"]
env:
VCENTER_URL: "https://10.20.92.50"
VCENTER_USER: "${VCENTER_USER}"
VCENTER_PASS: "${VCENTER_PASS}"
開發者建立 MCP Server(一個 .ts 檔案)
↓
在 mcp-config.yaml 加一段設定
↓
重啟 Agent(或支援熱載入)
↓
Supervisor 自動發現新 Tools
↓
使用者透過 Chat 直接使用
優點:
缺點: 比直接寫 Tool function 多一層抽象
讓 Agent 擁有執行 JavaScript/TypeScript 的能力,遇到不支援的資料源時自己寫 code 解決。
// src/tools/code-executor.ts
import { VM } from "vm2";
export const execute_code = tool(
async ({ code, description }) => {
const vm = new VM({
timeout: 10000, // 10 秒超時
sandbox: {
fetch, // 允許 HTTP 呼叫
console: { log: (...args) => logs.push(args.join(" ")) },
JSON,
Buffer,
},
});
const result = vm.run(code);
return JSON.stringify({ result, logs });
},
{
name: "execute_code",
description: "執行 JavaScript 程式碼。用於一次性資料處理或快速驗證。沙箱環境,有 fetch 可呼叫 API。",
schema: z.object({
code: z.string().describe("要執行的 JavaScript 程式碼"),
description: z.string().describe("這段程式碼在做什麼(供稽核用)"),
}),
}
);
使用情境:
使用者:「幫我算一下每台設備的磁碟剩餘天數,
用過去 7 天的增量推算」
Agent:現有 Tool 沒有趨勢預測功能,我寫一段程式來算。
[execute_code({
code: `
// 取得 7 天前和今天的磁碟使用率
const today = await fetch('...librenms.../storage').then(r => r.json());
const weekAgo = await fetch('...').then(r => r.json());
// 計算每日增量和預估滿載天數
const predictions = today.map((disk, i) => {
const dailyGrowth = (disk.used - weekAgo[i].used) / 7;
const remaining = disk.free / dailyGrowth;
return {
device: disk.hostname,
disk: disk.descr,
currentPercent: disk.percent,
dailyGrowthGB: (dailyGrowth / 1e9).toFixed(2),
daysUntilFull: Math.round(remaining),
};
});
return predictions.sort((a, b) => a.daysUntilFull - b.daysUntilFull);
`,
description: "計算磁碟剩餘天數預測"
})]
預測結果:
| 設備 | 磁碟 | 目前 | 日增 | 預估滿載 |
|------|------|------|------|---------|
| mes-db-2 | E: | 78% | 0.5 GB/天 | 約 90 天 |
| th-ex001 | D: | 72% | 0.2 GB/天 | 約 200 天 |
優點: 零限制,Agent 可以做任何運算
缺點: 安全風險(需要嚴格沙箱);執行結果不可預測
必要限制: vm2 沙箱 + 網路白名單 + 執行超時 + 稽核日誌
在 Chat UI 中提供引導式的連接器建立流程。
使用者:「我要加入一個新的資料源」
Agent:「好的,請告訴我:
1. 資料源名稱?
2. 連線方式?
○ REST API(最常見)
○ SSH
○ 資料庫(MySQL/PostgreSQL)
○ 檔案(CSV/JSON)」
使用者:「Fortinet FortiGate 防火牆,REST API」
Agent:「請提供:
- API URL:
- 認證方式:(API Key / 帳號密碼 / OAuth)
- 想查詢的資料:(VPN 連線 / Policy / Session / ...)」
使用者:「https://10.20.92.1/api/v2, API Key = xxx, 想看 VPN 連線」
Agent:讓我測試連線...
[http_request → 測試 /api/v2/monitor/vpn/ipsec]
✅ 連線成功!找到 3 條 IPSec 通道。
我已建立 Connector 設定:
┌──────────────────────────────────┐
│ 名稱:FortiGate VPN Monitor │
│ URL:https://10.20.92.1/api/v2 │
│ 認證:API Key(已加密儲存) │
│ 可查詢: │
│ • /monitor/vpn/ipsec — IPSec 通道│
│ • /monitor/vpn/ssl — SSL VPN │
│ • /monitor/system/status — 狀態 │
└──────────────────────────────────┘
要儲存嗎?儲存後我就能在報表中使用這個資料源。
使用者:「存」
Agent:[save → connectors/fortigate.yaml]
✅ 已儲存。以後你可以說「查 FortiGate 的 VPN 連線」,
我就會自動使用這個 Connector。
# connectors/fortigate.yaml — 自動產生
name: fortigate
display_name: FortiGate 防火牆
type: rest_api
base_url: https://10.20.92.1/api/v2
auth:
type: api_key
header: Authorization
value_ref: secrets/fortigate_api_key # 加密存儲
endpoints:
- name: vpn_ipsec
path: /monitor/vpn/ipsec
method: GET
description: "IPSec VPN 通道狀態"
- name: vpn_ssl
path: /monitor/vpn/ssl
method: GET
description: "SSL VPN 連線"
- name: system_status
path: /monitor/system/status
method: GET
description: "系統狀態"
created_by: kyle
created_at: 2026-03-26
開發團隊標準化的擴展方式。放入一個目錄就自動生效。
plugins/
├── librenms/ # 已內建
│ ├── plugin.yaml # 元資料 + Tool 清單
│ ├── tools.ts # Tool 實作
│ └── prompts.ts # Agent Prompt
├── graylog/ # 已內建
│ └── ...
├── vcenter/ # 新增!放入即生效
│ ├── plugin.yaml
│ ├── tools.ts
│ └── prompts.ts
└── fortigate/ # 新增!
└── ...
# plugins/vcenter/plugin.yaml
name: vcenter
version: 1.0.0
description: "VMware vCenter 整合"
author: arova-team
model:
default: ollama/qwen3.5:35b-a3b
tools:
- name: list_vms
description: "列出 vCenter 所有 VM"
- name: get_vm_metrics
description: "查詢 VM CPU/Memory"
env_required:
- VCENTER_URL
- VCENTER_USER
- VCENTER_PASS
不是只選一個,而是分層疊加:
┌─────────────────────────────────────────────────┐
│ Layer 4: Plugin 目錄(開發團隊正式交付) │
│ 最高品質、有測試、有文件 │
├─────────────────────────────────────────────────┤
│ Layer 3: MCP Server(標準化擴展) │
│ 獨立 process、隔離性好、可跨語言 │
├─────────────────────────────────────────────────┤
│ Layer 2: Low-Code Connector(使用者自助建立) │
│ Agent 引導、YAML 設定、自動測試 │
├─────────────────────────────────────────────────┤
│ Layer 1: Generic HTTP + SSH(萬能兜底) │
│ 零設定、Agent 自己組裝 API 呼叫 │
├─────────────────────────────────────────────────┤
│ Layer 0: Code Interpreter(終極兜底) │
│ Agent 自己寫程式碼解決 │
└─────────────────────────────────────────────────┘
| Phase | 實作 | 工期 |
|---|---|---|
| Phase 1(MVP) | Generic HTTP Tool + SSH Tool | 1 天 |
| Phase 2 | Low-Code Connector Builder(對話式建立 + YAML 儲存) | 3 天 |
| Phase 3 | MCP Server 架構(LibreNMS/Graylog 改為 MCP Server) | 1 週 |
| Phase 4 | Plugin 目錄熱載入 + Marketplace 概念 | 2 週 |
| Phase 5 | Code Interpreter(沙箱化) | 3 天 |
使用者需要新資料 →
│
├── 只是一次性查詢?
│ └── YES → Generic HTTP Tool(Agent 直接打 API)
│
├── 會重複使用?
│ └── YES → Low-Code Connector(引導建立 + 儲存 YAML)
│
├── 需要複雜邏輯或多步驟?
│ └── YES → MCP Server 或 Plugin(開發團隊處理)
│
└── 目標系統沒有 API?
└── YES → SSH Tool(直接查 log/指令輸出)