generated from rmcguire/go-server-with-otel
remove manual econet mcp
This commit is contained in:
@@ -1,64 +0,0 @@
|
|||||||
/*
|
|
||||||
Package econetmcp exposes an MCP server, mounted as an HTTP handler at
|
|
||||||
/api/mcp, whose tools wrap the EconetService gRPC API.
|
|
||||||
*/
|
|
||||||
package econetmcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"gitea.libretechconsulting.com/rmcguire/go-app/pkg/srv/http/opts"
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
||||||
"github.com/rs/zerolog"
|
|
||||||
|
|
||||||
"gitea.libretechconsulting.com/rmcguire/econet-exporter/pkg/config"
|
|
||||||
"gitea.libretechconsulting.com/rmcguire/econet-exporter/pkg/econet/econetgrpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
var EconetMCPImpl = &mcp.Implementation{
|
|
||||||
Name: "Econet MCP Server",
|
|
||||||
Title: "Econet Exporter MCP",
|
|
||||||
}
|
|
||||||
|
|
||||||
type EconetMCPServer struct {
|
|
||||||
ctx context.Context
|
|
||||||
cfg *config.ServiceConfig
|
|
||||||
log *zerolog.Logger
|
|
||||||
server *mcp.Server
|
|
||||||
econetGRPC *econetgrpc.EconetGRPCServer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEconetMCPServer(ctx context.Context, cfg *config.ServiceConfig,
|
|
||||||
grpc *econetgrpc.EconetGRPCServer,
|
|
||||||
) *EconetMCPServer {
|
|
||||||
return &EconetMCPServer{
|
|
||||||
ctx: ctx,
|
|
||||||
cfg: cfg,
|
|
||||||
log: zerolog.Ctx(ctx),
|
|
||||||
econetGRPC: grpc,
|
|
||||||
server: mcp.NewServer(EconetMCPImpl, &mcp.ServerOptions{
|
|
||||||
Instructions: "Use these tools to inspect Rheem EcoNet water heaters: " +
|
|
||||||
"current mode, setpoint, hot water availability, connectivity, and alerts.",
|
|
||||||
HasTools: true,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *EconetMCPServer) GetHandlers() []opts.HTTPHandler {
|
|
||||||
// NOTE: Add other tools here
|
|
||||||
m.addListDevicesTool()
|
|
||||||
|
|
||||||
handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server {
|
|
||||||
return m.server
|
|
||||||
}, &mcp.StreamableHTTPOptions{})
|
|
||||||
|
|
||||||
m.log.Debug().Msg("Econet MCP tools ready")
|
|
||||||
|
|
||||||
return []opts.HTTPHandler{
|
|
||||||
{
|
|
||||||
Prefix: "/api/mcp",
|
|
||||||
Handler: handler,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
package econetmcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
||||||
"k8s.io/utils/ptr"
|
|
||||||
|
|
||||||
pb "gitea.libretechconsulting.com/rmcguire/econet-exporter/api/econet/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ListDevicesTool = &mcp.Tool{
|
|
||||||
Name: "list_water_heaters",
|
|
||||||
Title: "List EcoNet Water Heaters",
|
|
||||||
Description: "Lists Rheem EcoNet devices and their current state (mode, setpoint, " +
|
|
||||||
"hot water availability, connectivity). Optionally filter to one serial number.",
|
|
||||||
Annotations: &mcp.ToolAnnotations{
|
|
||||||
ReadOnlyHint: true,
|
|
||||||
OpenWorldHint: ptr.To(true),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListDevicesParams struct {
|
|
||||||
SerialNumber string `json:"serial_number,omitempty" jsonschema:"Optional device serial number to return a single device"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *EconetMCPServer) addListDevicesTool() {
|
|
||||||
mcp.AddTool(m.server, ListDevicesTool, m.listDevicesHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *EconetMCPServer) listDevicesHandler(ctx context.Context, _ *mcp.CallToolRequest, args *ListDevicesParams) (
|
|
||||||
*mcp.CallToolResult, any, error,
|
|
||||||
) {
|
|
||||||
m.log.Debug().Str("serial", args.SerialNumber).Msg("list water heaters tool called")
|
|
||||||
|
|
||||||
devices, err := m.lookup(ctx, args.SerialNumber)
|
|
||||||
if err != nil {
|
|
||||||
return &mcp.CallToolResult{IsError: true, Meta: mcp.Meta{"error": err.Error()}}, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &mcp.CallToolResult{
|
|
||||||
Content: []mcp.Content{&mcp.TextContent{Text: summarizeDevices(devices)}},
|
|
||||||
}, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *EconetMCPServer) lookup(ctx context.Context, serial string) ([]*pb.Device, error) {
|
|
||||||
if serial != "" {
|
|
||||||
resp, err := m.econetGRPC.GetDevice(ctx, &pb.GetDeviceRequest{SerialNumber: serial})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return []*pb.Device{resp.GetDevice()}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := m.econetGRPC.ListDevices(ctx, &pb.ListDevicesRequest{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return resp.GetDevices(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func summarizeDevices(devices []*pb.Device) string {
|
|
||||||
if len(devices) == 0 {
|
|
||||||
return "No EcoNet devices found."
|
|
||||||
}
|
|
||||||
var b strings.Builder
|
|
||||||
for _, d := range devices {
|
|
||||||
fmt.Fprintf(&b, "%s (%s, serial %s): mode=%s setpoint=%d°F running=%t connected=%t hotWater=%d%% alerts=%d\n",
|
|
||||||
d.GetFriendlyName(), d.GetGenericType(), d.GetSerialNumber(),
|
|
||||||
d.GetMode(), d.GetSetpoint(), d.GetRunning(), d.GetConnected(),
|
|
||||||
d.GetHotWaterAvailability(), d.GetAlertCount())
|
|
||||||
}
|
|
||||||
return strings.TrimRight(b.String(), "\n")
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package econetmcp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
pb "gitea.libretechconsulting.com/rmcguire/econet-exporter/api/econet/v1alpha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSummarizeDevices(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
devices []*pb.Device
|
|
||||||
want string // substring that must appear
|
|
||||||
}{
|
|
||||||
{"empty", nil, "No EcoNet devices found."},
|
|
||||||
{
|
|
||||||
name: "single",
|
|
||||||
devices: []*pb.Device{{
|
|
||||||
FriendlyName: "Garage",
|
|
||||||
GenericType: "heatpumpWaterHeater",
|
|
||||||
SerialNumber: "ABC123",
|
|
||||||
Mode: "heat-pump",
|
|
||||||
Setpoint: 125,
|
|
||||||
}},
|
|
||||||
want: "Garage (heatpumpWaterHeater, serial ABC123): mode=heat-pump setpoint=125",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got := summarizeDevices(tt.devices)
|
|
||||||
if !strings.Contains(got, tt.want) {
|
|
||||||
t.Errorf("summarizeDevices() = %q, want substring %q", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user