mirror of
https://github.com/anand34577/ferrum.git
synced 2026-09-12 05:48:58 +00:00
22c1dd382c
- User-scoped API keys (Profile > API Keys) for 3rd-party REST API access
and MCP clients, each locked to one scope at creation, with expiry,
revocation, and last-used tracking.
- A hand-rolled MCP (Model Context Protocol) server exposing the fleet
(connections, nodes, guests, storage, pools, alerts, cluster status) as
read tools plus one admin-gated power-action tool, so Claude Code/Desktop
or any other MCP client can query and operate the fleet directly.
- Both the REST API and MCP are off by default and toggleable instance-wide
from Settings > API & MCP, enforced live on every request.
- Admin-managed AI providers (any OpenAI-chat-completions-compatible
endpoint) backing the AI Assistant's tool-calling loop, replacing the
single hardcoded provider.
- A built-in, zero-config, no-API-key local provider backed by Needle 2
(internal/needle) for fully offline tool-calling, wired in as a one-click
preset. Requires the operator to separately download the Needle 2 binary
and point FERRUM_NEEDLE_BIN at it -- Ferrum never fetches executable
content from the network itself; see README "Built-in LLM (Needle 2)".
- System settings (CORS allow-list, instance-wide toggles) moved to the
admin Settings UI; environment variables are now scoped to true
bootstrap-level config only (listen address, TLS, DB connection, secret,
optional Needle binary path).
- Fixed: node Journal tab 502'ing with "unexpected end of JSON input" on an
empty response, and separately with a decode error on PVE versions that
return a bare-string journal line instead of the documented {n,t} object.
- Fixed: bottom content padding disappearing on every page except the AI
Assistant (an unconditional h-full on the content wrapper let overflowing
content bleed through where the padding should render).
- Fixed: Profile page felt cramped despite a wide viewport (stray max-w-2xl
cap not present on the equivalent Settings page).
- Test coverage added for the previously-untested MCP package and the new
Needle adapter (20 new Go tests), plus a regression test for the journal
decode fix.
87 lines
2.4 KiB
Go
87 lines
2.4 KiB
Go
// Package mcp implements a minimal Model Context Protocol server — the
|
|
// "tools/list" + "tools/call" subset over the Streamable HTTP transport
|
|
// (JSON-RPC 2.0, single POST endpoint) — so Ferrum can be added as a remote
|
|
// MCP server in Claude Desktop/Claude Code. It's hand-rolled rather than
|
|
// built on an external SDK to keep the dependency surface (and offline build
|
|
// risk) at zero; the protocol subset used here is small and stable.
|
|
package mcp
|
|
|
|
import "encoding/json"
|
|
|
|
const ProtocolVersion = "2025-06-18"
|
|
|
|
type request struct {
|
|
JSONRPC string `json:"jsonrpc"`
|
|
ID json.RawMessage `json:"id,omitempty"`
|
|
Method string `json:"method"`
|
|
Params json.RawMessage `json:"params,omitempty"`
|
|
}
|
|
|
|
type response struct {
|
|
JSONRPC string `json:"jsonrpc"`
|
|
ID json.RawMessage `json:"id,omitempty"`
|
|
Result any `json:"result,omitempty"`
|
|
Error *rpcError `json:"error,omitempty"`
|
|
}
|
|
|
|
type rpcError struct {
|
|
Code int `json:"code"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func errorResponse(id json.RawMessage, code int, msg string) response {
|
|
return response{JSONRPC: "2.0", ID: id, Error: &rpcError{Code: code, Message: msg}}
|
|
}
|
|
|
|
func resultResponse(id json.RawMessage, result any) response {
|
|
return response{JSONRPC: "2.0", ID: id, Result: result}
|
|
}
|
|
|
|
// --- initialize ---
|
|
|
|
type initializeResult struct {
|
|
ProtocolVersion string `json:"protocolVersion"`
|
|
Capabilities map[string]any `json:"capabilities"`
|
|
ServerInfo serverInfo `json:"serverInfo"`
|
|
}
|
|
|
|
type serverInfo struct {
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
// --- tools ---
|
|
|
|
type Tool struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
InputSchema map[string]any `json:"inputSchema"`
|
|
}
|
|
|
|
type toolsListResult struct {
|
|
Tools []Tool `json:"tools"`
|
|
}
|
|
|
|
type toolCallParams struct {
|
|
Name string `json:"name"`
|
|
Arguments json.RawMessage `json:"arguments"`
|
|
}
|
|
|
|
type content struct {
|
|
Type string `json:"type"`
|
|
Text string `json:"text"`
|
|
}
|
|
|
|
type toolCallResult struct {
|
|
Content []content `json:"content"`
|
|
IsError bool `json:"isError,omitempty"`
|
|
}
|
|
|
|
func textResult(text string) toolCallResult {
|
|
return toolCallResult{Content: []content{{Type: "text", Text: text}}}
|
|
}
|
|
|
|
func errorResult(text string) toolCallResult {
|
|
return toolCallResult{Content: []content{{Type: "text", Text: text}}, IsError: true}
|
|
}
|