1 Commits

Author SHA1 Message Date
bf02935716 Add agent version to mcp node list
Thread the linker-injected version string into the Agent struct and
return it in the NodeStatus RPC. The CLI now dials each node and
displays the agent version alongside name and address.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 17:49:49 -07:00
6 changed files with 44 additions and 6 deletions

View File

@@ -38,7 +38,7 @@ func main() {
if err != nil {
return fmt.Errorf("load config: %w", err)
}
return agent.Run(cfg)
return agent.Run(cfg, version)
},
})

View File

@@ -1,12 +1,15 @@
package main
import (
"context"
"fmt"
"os"
"text/tabwriter"
"time"
toml "github.com/pelletier/go-toml/v2"
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
"git.wntrmute.dev/mc/mcp/internal/config"
"github.com/spf13/cobra"
)
@@ -48,13 +51,35 @@ func runNodeList(_ *cobra.Command, _ []string) error {
}
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
_, _ = fmt.Fprintln(w, "NAME\tADDRESS")
_, _ = fmt.Fprintln(w, "NAME\tADDRESS\tVERSION")
for _, n := range cfg.Nodes {
_, _ = fmt.Fprintf(w, "%s\t%s\n", n.Name, n.Address)
ver := queryAgentVersion(cfg, n.Address)
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\n", n.Name, n.Address, ver)
}
return w.Flush()
}
// queryAgentVersion dials the agent and returns its version, or an error indicator.
func queryAgentVersion(cfg *config.CLIConfig, address string) string {
client, conn, err := dialAgent(address, cfg)
if err != nil {
return "error"
}
defer func() { _ = conn.Close() }()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.NodeStatus(ctx, &mcpv1.NodeStatusRequest{})
if err != nil {
return "error"
}
if resp.AgentVersion == "" {
return "unknown"
}
return resp.AgentVersion
}
func runNodeAdd(_ *cobra.Command, args []string) error {
cfg, err := config.LoadCLIConfig(cfgPath)
if err != nil {

View File

@@ -1926,6 +1926,7 @@ type NodeStatusResponse struct {
MemoryFreeBytes uint64 `protobuf:"varint,9,opt,name=memory_free_bytes,json=memoryFreeBytes,proto3" json:"memory_free_bytes,omitempty"`
CpuUsagePercent float64 `protobuf:"fixed64,10,opt,name=cpu_usage_percent,json=cpuUsagePercent,proto3" json:"cpu_usage_percent,omitempty"`
UptimeSince *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=uptime_since,json=uptimeSince,proto3" json:"uptime_since,omitempty"`
AgentVersion string `protobuf:"bytes,12,opt,name=agent_version,json=agentVersion,proto3" json:"agent_version,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@@ -2037,6 +2038,13 @@ func (x *NodeStatusResponse) GetUptimeSince() *timestamppb.Timestamp {
return nil
}
func (x *NodeStatusResponse) GetAgentVersion() string {
if x != nil {
return x.AgentVersion
}
return ""
}
type PurgeRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Service name (empty = all services).
@@ -2474,7 +2482,7 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
"\acontent\x18\x01 \x01(\fR\acontent\x12\x12\n" +
"\x04mode\x18\x02 \x01(\rR\x04mode\x12\x14\n" +
"\x05error\x18\x03 \x01(\tR\x05error\"\x13\n" +
"\x11NodeStatusRequest\"\xd9\x03\n" +
"\x11NodeStatusRequest\"\xfe\x03\n" +
"\x12NodeStatusResponse\x12\x1b\n" +
"\tnode_name\x18\x01 \x01(\tR\bnodeName\x12\x18\n" +
"\aruntime\x18\x02 \x01(\tR\aruntime\x12'\n" +
@@ -2487,7 +2495,8 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
"\x11memory_free_bytes\x18\t \x01(\x04R\x0fmemoryFreeBytes\x12*\n" +
"\x11cpu_usage_percent\x18\n" +
" \x01(\x01R\x0fcpuUsagePercent\x12=\n" +
"\fuptime_since\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\vuptimeSince\"\x8e\x01\n" +
"\fuptime_since\x18\v \x01(\v2\x1a.google.protobuf.TimestampR\vuptimeSince\x12#\n" +
"\ragent_version\x18\f \x01(\tR\fagentVersion\"\x8e\x01\n" +
"\fPurgeRequest\x12\x18\n" +
"\aservice\x18\x01 \x01(\tR\aservice\x12\x1c\n" +
"\tcomponent\x18\x02 \x01(\tR\tcomponent\x12\x17\n" +

View File

@@ -35,11 +35,12 @@ type Agent struct {
Proxy *ProxyRouter
Certs *CertProvisioner
DNS *DNSRegistrar
Version string
}
// Run starts the agent: opens the database, sets up the gRPC server with
// TLS and auth, and blocks until SIGINT/SIGTERM.
func Run(cfg *config.AgentConfig) error {
func Run(cfg *config.AgentConfig, version string) error {
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: parseLogLevel(cfg.Log.Level),
}))
@@ -79,6 +80,7 @@ func Run(cfg *config.AgentConfig) error {
Proxy: proxy,
Certs: certs,
DNS: dns,
Version: version,
}
tlsCert, err := tls.LoadX509KeyPair(cfg.Server.TLSCert, cfg.Server.TLSKey)

View File

@@ -31,6 +31,7 @@ func (a *Agent) NodeStatus(ctx context.Context, _ *mcpv1.NodeStatusRequest) (*mc
Runtime: a.Config.Agent.ContainerRuntime,
ServiceCount: uint32(len(services)), //nolint:gosec // bounded
ComponentCount: componentCount,
AgentVersion: a.Version,
}
// Runtime version.

View File

@@ -257,6 +257,7 @@ message NodeStatusResponse {
uint64 memory_free_bytes = 9;
double cpu_usage_percent = 10;
google.protobuf.Timestamp uptime_since = 11;
string agent_version = 12;
}
// --- Purge ---