2 Commits

Author SHA1 Message Date
d8f45ca520 Merge explicit ports with route-allocated ports during deploy
Previously, explicit port mappings from the service definition were
ignored when routes were present. Now both are included, allowing
services to have stable external port bindings alongside dynamic
route-allocated ports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:28:40 -07:00
95f86157b4 Add mcp route command for managing mc-proxy routes
New top-level command with list, add, remove subcommands. Supports
-n/--node to target a specific node. Adds AddProxyRoute and
RemoveProxyRoute RPCs to the agent. Moves route listing from
mcp node routes to mcp route list.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:10:11 -07:00
9 changed files with 643 additions and 83 deletions

View File

@@ -53,6 +53,7 @@ func main() {
root.AddCommand(logsCmd())
root.AddCommand(editCmd())
root.AddCommand(dnsCmd())
root.AddCommand(routeCmd())
if err := root.Execute(); err != nil {
log.Fatal(err)

View File

@@ -40,62 +40,10 @@ func nodeCmd() *cobra.Command {
RunE: runNodeRemove,
}
routes := &cobra.Command{
Use: "routes",
Short: "List mc-proxy routes on all nodes",
RunE: runNodeRoutes,
}
cmd.AddCommand(list, add, remove, routes)
cmd.AddCommand(list, add, remove)
return cmd
}
func runNodeRoutes(_ *cobra.Command, _ []string) error {
first := true
return forEachNode(func(node config.NodeConfig, client mcpv1.McpAgentServiceClient) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.ListProxyRoutes(ctx, &mcpv1.ListProxyRoutesRequest{})
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "warning: %s: list routes: %v\n", node.Name, err)
return nil
}
if !first {
fmt.Println()
}
first = false
fmt.Printf("NODE: %s\n", node.Name)
fmt.Printf("mc-proxy %s\n", resp.GetVersion())
if resp.GetStartedAt() != nil {
uptime := time.Since(resp.GetStartedAt().AsTime()).Truncate(time.Second)
fmt.Printf("uptime: %s\n", uptime)
}
fmt.Printf("connections: %d\n", resp.GetTotalConnections())
fmt.Println()
for _, ls := range resp.GetListeners() {
fmt.Printf(" %s routes=%d active=%d\n",
ls.GetAddr(), ls.GetRouteCount(), ls.GetActiveConnections())
for _, r := range ls.GetRoutes() {
mode := r.GetMode()
if mode == "" {
mode = "l4"
}
extra := ""
if r.GetBackendTls() {
extra = " (re-encrypt)"
}
fmt.Printf(" %s %s → %s%s\n", mode, r.GetHostname(), r.GetBackend(), extra)
}
}
return nil
})
}
func runNodeList(_ *cobra.Command, _ []string) error {
cfg, err := config.LoadCLIConfig(cfgPath)
if err != nil {

212
cmd/mcp/route.go Normal file
View File

@@ -0,0 +1,212 @@
package main
import (
"context"
"fmt"
"os"
"time"
"github.com/spf13/cobra"
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
"git.wntrmute.dev/mc/mcp/internal/config"
)
func routeCmd() *cobra.Command {
var nodeName string
cmd := &cobra.Command{
Use: "route",
Short: "Manage mc-proxy routes",
}
list := &cobra.Command{
Use: "list",
Short: "List mc-proxy routes",
RunE: func(_ *cobra.Command, _ []string) error {
return runRouteList(nodeName)
},
}
add := &cobra.Command{
Use: "add <listener> <hostname> <backend>",
Short: "Add a route to mc-proxy",
Long: "Add a route. Example: mcp route add -n rift :443 mcq.metacircular.net 100.95.252.120:443",
Args: cobra.ExactArgs(3),
RunE: func(_ *cobra.Command, args []string) error {
return runRouteAdd(nodeName, args)
},
}
add.Flags().String("mode", "l4", "route mode (l4 or l7)")
add.Flags().Bool("backend-tls", false, "re-encrypt traffic to backend")
remove := &cobra.Command{
Use: "remove <listener> <hostname>",
Short: "Remove a route from mc-proxy",
Long: "Remove a route. Example: mcp route remove -n rift :443 mcq.metacircular.net",
Args: cobra.ExactArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
return runRouteRemove(nodeName, args)
},
}
cmd.PersistentFlags().StringVarP(&nodeName, "node", "n", "", "target node (required)")
cmd.AddCommand(list, add, remove)
return cmd
}
func runRouteList(nodeName string) error {
if nodeName == "" {
return runRouteListAll()
}
cfg, err := config.LoadCLIConfig(cfgPath)
if err != nil {
return fmt.Errorf("load config: %w", err)
}
address, err := findNodeAddress(cfg, nodeName)
if err != nil {
return err
}
client, conn, err := dialAgent(address, cfg)
if err != nil {
return fmt.Errorf("dial agent: %w", err)
}
defer func() { _ = conn.Close() }()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.ListProxyRoutes(ctx, &mcpv1.ListProxyRoutesRequest{})
if err != nil {
return fmt.Errorf("list routes: %w", err)
}
printRoutes(nodeName, resp)
return nil
}
func runRouteListAll() error {
first := true
return forEachNode(func(node config.NodeConfig, client mcpv1.McpAgentServiceClient) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.ListProxyRoutes(ctx, &mcpv1.ListProxyRoutesRequest{})
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "warning: %s: list routes: %v\n", node.Name, err)
return nil
}
if !first {
fmt.Println()
}
first = false
printRoutes(node.Name, resp)
return nil
})
}
func printRoutes(nodeName string, resp *mcpv1.ListProxyRoutesResponse) {
fmt.Printf("NODE: %s\n", nodeName)
fmt.Printf("mc-proxy %s\n", resp.GetVersion())
if resp.GetStartedAt() != nil {
uptime := time.Since(resp.GetStartedAt().AsTime()).Truncate(time.Second)
fmt.Printf("uptime: %s\n", uptime)
}
fmt.Printf("connections: %d\n", resp.GetTotalConnections())
fmt.Println()
for _, ls := range resp.GetListeners() {
fmt.Printf(" %s routes=%d active=%d\n",
ls.GetAddr(), ls.GetRouteCount(), ls.GetActiveConnections())
for _, r := range ls.GetRoutes() {
mode := r.GetMode()
if mode == "" {
mode = "l4"
}
extra := ""
if r.GetBackendTls() {
extra = " (re-encrypt)"
}
fmt.Printf(" %s %s → %s%s\n", mode, r.GetHostname(), r.GetBackend(), extra)
}
}
}
func runRouteAdd(nodeName string, args []string) error {
if nodeName == "" {
return fmt.Errorf("--node is required")
}
cfg, err := config.LoadCLIConfig(cfgPath)
if err != nil {
return fmt.Errorf("load config: %w", err)
}
address, err := findNodeAddress(cfg, nodeName)
if err != nil {
return err
}
client, conn, err := dialAgent(address, cfg)
if err != nil {
return fmt.Errorf("dial agent: %w", err)
}
defer func() { _ = conn.Close() }()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err = client.AddProxyRoute(ctx, &mcpv1.AddProxyRouteRequest{
ListenerAddr: args[0],
Hostname: args[1],
Backend: args[2],
})
if err != nil {
return fmt.Errorf("add route: %w", err)
}
fmt.Printf("Added route: %s → %s on %s (%s)\n", args[1], args[2], args[0], nodeName)
return nil
}
func runRouteRemove(nodeName string, args []string) error {
if nodeName == "" {
return fmt.Errorf("--node is required")
}
cfg, err := config.LoadCLIConfig(cfgPath)
if err != nil {
return fmt.Errorf("load config: %w", err)
}
address, err := findNodeAddress(cfg, nodeName)
if err != nil {
return err
}
client, conn, err := dialAgent(address, cfg)
if err != nil {
return fmt.Errorf("dial agent: %w", err)
}
defer func() { _ = conn.Close() }()
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_, err = client.RemoveProxyRoute(ctx, &mcpv1.RemoveProxyRouteRequest{
ListenerAddr: args[0],
Hostname: args[1],
})
if err != nil {
return fmt.Errorf("remove route: %w", err)
}
fmt.Printf("Removed route: %s from %s (%s)\n", args[1], args[0], nodeName)
return nil
}

View File

@@ -2808,6 +2808,206 @@ func (x *ListProxyRoutesResponse) GetListeners() []*ProxyListenerInfo {
return nil
}
type AddProxyRouteRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
ListenerAddr string `protobuf:"bytes,1,opt,name=listener_addr,json=listenerAddr,proto3" json:"listener_addr,omitempty"` // e.g. ":443"
Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"`
Backend string `protobuf:"bytes,3,opt,name=backend,proto3" json:"backend,omitempty"`
Mode string `protobuf:"bytes,4,opt,name=mode,proto3" json:"mode,omitempty"` // "l4" or "l7"
BackendTls bool `protobuf:"varint,5,opt,name=backend_tls,json=backendTls,proto3" json:"backend_tls,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AddProxyRouteRequest) Reset() {
*x = AddProxyRouteRequest{}
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[49]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AddProxyRouteRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddProxyRouteRequest) ProtoMessage() {}
func (x *AddProxyRouteRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[49]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddProxyRouteRequest.ProtoReflect.Descriptor instead.
func (*AddProxyRouteRequest) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{49}
}
func (x *AddProxyRouteRequest) GetListenerAddr() string {
if x != nil {
return x.ListenerAddr
}
return ""
}
func (x *AddProxyRouteRequest) GetHostname() string {
if x != nil {
return x.Hostname
}
return ""
}
func (x *AddProxyRouteRequest) GetBackend() string {
if x != nil {
return x.Backend
}
return ""
}
func (x *AddProxyRouteRequest) GetMode() string {
if x != nil {
return x.Mode
}
return ""
}
func (x *AddProxyRouteRequest) GetBackendTls() bool {
if x != nil {
return x.BackendTls
}
return false
}
type AddProxyRouteResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *AddProxyRouteResponse) Reset() {
*x = AddProxyRouteResponse{}
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[50]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *AddProxyRouteResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AddProxyRouteResponse) ProtoMessage() {}
func (x *AddProxyRouteResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[50]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AddProxyRouteResponse.ProtoReflect.Descriptor instead.
func (*AddProxyRouteResponse) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{50}
}
type RemoveProxyRouteRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
ListenerAddr string `protobuf:"bytes,1,opt,name=listener_addr,json=listenerAddr,proto3" json:"listener_addr,omitempty"` // e.g. ":443"
Hostname string `protobuf:"bytes,2,opt,name=hostname,proto3" json:"hostname,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RemoveProxyRouteRequest) Reset() {
*x = RemoveProxyRouteRequest{}
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[51]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RemoveProxyRouteRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RemoveProxyRouteRequest) ProtoMessage() {}
func (x *RemoveProxyRouteRequest) ProtoReflect() protoreflect.Message {
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[51]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RemoveProxyRouteRequest.ProtoReflect.Descriptor instead.
func (*RemoveProxyRouteRequest) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{51}
}
func (x *RemoveProxyRouteRequest) GetListenerAddr() string {
if x != nil {
return x.ListenerAddr
}
return ""
}
func (x *RemoveProxyRouteRequest) GetHostname() string {
if x != nil {
return x.Hostname
}
return ""
}
type RemoveProxyRouteResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *RemoveProxyRouteResponse) Reset() {
*x = RemoveProxyRouteResponse{}
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[52]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *RemoveProxyRouteResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*RemoveProxyRouteResponse) ProtoMessage() {}
func (x *RemoveProxyRouteResponse) ProtoReflect() protoreflect.Message {
mi := &file_proto_mcp_v1_mcp_proto_msgTypes[52]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use RemoveProxyRouteResponse.ProtoReflect.Descriptor instead.
func (*RemoveProxyRouteResponse) Descriptor() ([]byte, []int) {
return file_proto_mcp_v1_mcp_proto_rawDescGZIP(), []int{52}
}
var File_proto_mcp_v1_mcp_proto protoreflect.FileDescriptor
const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
@@ -2998,7 +3198,19 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
"\x11total_connections\x18\x02 \x01(\x03R\x10totalConnections\x129\n" +
"\n" +
"started_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\tstartedAt\x127\n" +
"\tlisteners\x18\x04 \x03(\v2\x19.mcp.v1.ProxyListenerInfoR\tlisteners2\xed\t\n" +
"\tlisteners\x18\x04 \x03(\v2\x19.mcp.v1.ProxyListenerInfoR\tlisteners\"\xa6\x01\n" +
"\x14AddProxyRouteRequest\x12#\n" +
"\rlistener_addr\x18\x01 \x01(\tR\flistenerAddr\x12\x1a\n" +
"\bhostname\x18\x02 \x01(\tR\bhostname\x12\x18\n" +
"\abackend\x18\x03 \x01(\tR\abackend\x12\x12\n" +
"\x04mode\x18\x04 \x01(\tR\x04mode\x12\x1f\n" +
"\vbackend_tls\x18\x05 \x01(\bR\n" +
"backendTls\"\x17\n" +
"\x15AddProxyRouteResponse\"Z\n" +
"\x17RemoveProxyRouteRequest\x12#\n" +
"\rlistener_addr\x18\x01 \x01(\tR\flistenerAddr\x12\x1a\n" +
"\bhostname\x18\x02 \x01(\tR\bhostname\"\x1a\n" +
"\x18RemoveProxyRouteResponse2\x92\v\n" +
"\x0fMcpAgentService\x127\n" +
"\x06Deploy\x12\x15.mcp.v1.DeployRequest\x1a\x16.mcp.v1.DeployResponse\x12R\n" +
"\x0fUndeployService\x12\x1e.mcp.v1.UndeployServiceRequest\x1a\x1f.mcp.v1.UndeployServiceResponse\x12F\n" +
@@ -3016,7 +3228,9 @@ const file_proto_mcp_v1_mcp_proto_rawDesc = "" +
"\n" +
"NodeStatus\x12\x19.mcp.v1.NodeStatusRequest\x1a\x1a.mcp.v1.NodeStatusResponse\x12O\n" +
"\x0eListDNSRecords\x12\x1d.mcp.v1.ListDNSRecordsRequest\x1a\x1e.mcp.v1.ListDNSRecordsResponse\x12R\n" +
"\x0fListProxyRoutes\x12\x1e.mcp.v1.ListProxyRoutesRequest\x1a\x1f.mcp.v1.ListProxyRoutesResponse\x123\n" +
"\x0fListProxyRoutes\x12\x1e.mcp.v1.ListProxyRoutesRequest\x1a\x1f.mcp.v1.ListProxyRoutesResponse\x12L\n" +
"\rAddProxyRoute\x12\x1c.mcp.v1.AddProxyRouteRequest\x1a\x1d.mcp.v1.AddProxyRouteResponse\x12U\n" +
"\x10RemoveProxyRoute\x12\x1f.mcp.v1.RemoveProxyRouteRequest\x1a .mcp.v1.RemoveProxyRouteResponse\x123\n" +
"\x04Logs\x12\x13.mcp.v1.LogsRequest\x1a\x14.mcp.v1.LogsResponse0\x01B*Z(git.wntrmute.dev/mc/mcp/gen/mcp/v1;mcpv1b\x06proto3"
var (
@@ -3031,7 +3245,7 @@ func file_proto_mcp_v1_mcp_proto_rawDescGZIP() []byte {
return file_proto_mcp_v1_mcp_proto_rawDescData
}
var file_proto_mcp_v1_mcp_proto_msgTypes = make([]protoimpl.MessageInfo, 49)
var file_proto_mcp_v1_mcp_proto_msgTypes = make([]protoimpl.MessageInfo, 53)
var file_proto_mcp_v1_mcp_proto_goTypes = []any{
(*RouteSpec)(nil), // 0: mcp.v1.RouteSpec
(*ComponentSpec)(nil), // 1: mcp.v1.ComponentSpec
@@ -3082,7 +3296,11 @@ var file_proto_mcp_v1_mcp_proto_goTypes = []any{
(*ProxyRouteInfo)(nil), // 46: mcp.v1.ProxyRouteInfo
(*ProxyListenerInfo)(nil), // 47: mcp.v1.ProxyListenerInfo
(*ListProxyRoutesResponse)(nil), // 48: mcp.v1.ListProxyRoutesResponse
(*timestamppb.Timestamp)(nil), // 49: google.protobuf.Timestamp
(*AddProxyRouteRequest)(nil), // 49: mcp.v1.AddProxyRouteRequest
(*AddProxyRouteResponse)(nil), // 50: mcp.v1.AddProxyRouteResponse
(*RemoveProxyRouteRequest)(nil), // 51: mcp.v1.RemoveProxyRouteRequest
(*RemoveProxyRouteResponse)(nil), // 52: mcp.v1.RemoveProxyRouteResponse
(*timestamppb.Timestamp)(nil), // 53: google.protobuf.Timestamp
}
var file_proto_mcp_v1_mcp_proto_depIdxs = []int32{
0, // 0: mcp.v1.ComponentSpec.routes:type_name -> mcp.v1.RouteSpec
@@ -3096,20 +3314,20 @@ var file_proto_mcp_v1_mcp_proto_depIdxs = []int32{
2, // 8: mcp.v1.SyncDesiredStateRequest.services:type_name -> mcp.v1.ServiceSpec
16, // 9: mcp.v1.SyncDesiredStateResponse.results:type_name -> mcp.v1.ServiceSyncResult
19, // 10: mcp.v1.ServiceInfo.components:type_name -> mcp.v1.ComponentInfo
49, // 11: mcp.v1.ComponentInfo.started:type_name -> google.protobuf.Timestamp
53, // 11: mcp.v1.ComponentInfo.started:type_name -> google.protobuf.Timestamp
18, // 12: mcp.v1.ListServicesResponse.services:type_name -> mcp.v1.ServiceInfo
49, // 13: mcp.v1.EventInfo.timestamp:type_name -> google.protobuf.Timestamp
53, // 13: mcp.v1.EventInfo.timestamp:type_name -> google.protobuf.Timestamp
18, // 14: mcp.v1.GetServiceStatusResponse.services:type_name -> mcp.v1.ServiceInfo
22, // 15: mcp.v1.GetServiceStatusResponse.drift:type_name -> mcp.v1.DriftInfo
23, // 16: mcp.v1.GetServiceStatusResponse.recent_events:type_name -> mcp.v1.EventInfo
18, // 17: mcp.v1.LiveCheckResponse.services:type_name -> mcp.v1.ServiceInfo
28, // 18: mcp.v1.AdoptContainersResponse.results:type_name -> mcp.v1.AdoptResult
49, // 19: mcp.v1.NodeStatusResponse.uptime_since:type_name -> google.protobuf.Timestamp
53, // 19: mcp.v1.NodeStatusResponse.uptime_since:type_name -> google.protobuf.Timestamp
38, // 20: mcp.v1.PurgeResponse.results:type_name -> mcp.v1.PurgeResult
43, // 21: mcp.v1.DNSZone.records:type_name -> mcp.v1.DNSRecord
42, // 22: mcp.v1.ListDNSRecordsResponse.zones:type_name -> mcp.v1.DNSZone
46, // 23: mcp.v1.ProxyListenerInfo.routes:type_name -> mcp.v1.ProxyRouteInfo
49, // 24: mcp.v1.ListProxyRoutesResponse.started_at:type_name -> google.protobuf.Timestamp
53, // 24: mcp.v1.ListProxyRoutesResponse.started_at:type_name -> google.protobuf.Timestamp
47, // 25: mcp.v1.ListProxyRoutesResponse.listeners:type_name -> mcp.v1.ProxyListenerInfo
3, // 26: mcp.v1.McpAgentService.Deploy:input_type -> mcp.v1.DeployRequest
12, // 27: mcp.v1.McpAgentService.UndeployService:input_type -> mcp.v1.UndeployServiceRequest
@@ -3127,26 +3345,30 @@ var file_proto_mcp_v1_mcp_proto_depIdxs = []int32{
34, // 39: mcp.v1.McpAgentService.NodeStatus:input_type -> mcp.v1.NodeStatusRequest
41, // 40: mcp.v1.McpAgentService.ListDNSRecords:input_type -> mcp.v1.ListDNSRecordsRequest
45, // 41: mcp.v1.McpAgentService.ListProxyRoutes:input_type -> mcp.v1.ListProxyRoutesRequest
39, // 42: mcp.v1.McpAgentService.Logs:input_type -> mcp.v1.LogsRequest
4, // 43: mcp.v1.McpAgentService.Deploy:output_type -> mcp.v1.DeployResponse
13, // 44: mcp.v1.McpAgentService.UndeployService:output_type -> mcp.v1.UndeployServiceResponse
7, // 45: mcp.v1.McpAgentService.StopService:output_type -> mcp.v1.StopServiceResponse
9, // 46: mcp.v1.McpAgentService.StartService:output_type -> mcp.v1.StartServiceResponse
11, // 47: mcp.v1.McpAgentService.RestartService:output_type -> mcp.v1.RestartServiceResponse
15, // 48: mcp.v1.McpAgentService.SyncDesiredState:output_type -> mcp.v1.SyncDesiredStateResponse
20, // 49: mcp.v1.McpAgentService.ListServices:output_type -> mcp.v1.ListServicesResponse
24, // 50: mcp.v1.McpAgentService.GetServiceStatus:output_type -> mcp.v1.GetServiceStatusResponse
26, // 51: mcp.v1.McpAgentService.LiveCheck:output_type -> mcp.v1.LiveCheckResponse
29, // 52: mcp.v1.McpAgentService.AdoptContainers:output_type -> mcp.v1.AdoptContainersResponse
37, // 53: mcp.v1.McpAgentService.PurgeComponent:output_type -> mcp.v1.PurgeResponse
31, // 54: mcp.v1.McpAgentService.PushFile:output_type -> mcp.v1.PushFileResponse
33, // 55: mcp.v1.McpAgentService.PullFile:output_type -> mcp.v1.PullFileResponse
35, // 56: mcp.v1.McpAgentService.NodeStatus:output_type -> mcp.v1.NodeStatusResponse
44, // 57: mcp.v1.McpAgentService.ListDNSRecords:output_type -> mcp.v1.ListDNSRecordsResponse
48, // 58: mcp.v1.McpAgentService.ListProxyRoutes:output_type -> mcp.v1.ListProxyRoutesResponse
40, // 59: mcp.v1.McpAgentService.Logs:output_type -> mcp.v1.LogsResponse
43, // [43:60] is the sub-list for method output_type
26, // [26:43] is the sub-list for method input_type
49, // 42: mcp.v1.McpAgentService.AddProxyRoute:input_type -> mcp.v1.AddProxyRouteRequest
51, // 43: mcp.v1.McpAgentService.RemoveProxyRoute:input_type -> mcp.v1.RemoveProxyRouteRequest
39, // 44: mcp.v1.McpAgentService.Logs:input_type -> mcp.v1.LogsRequest
4, // 45: mcp.v1.McpAgentService.Deploy:output_type -> mcp.v1.DeployResponse
13, // 46: mcp.v1.McpAgentService.UndeployService:output_type -> mcp.v1.UndeployServiceResponse
7, // 47: mcp.v1.McpAgentService.StopService:output_type -> mcp.v1.StopServiceResponse
9, // 48: mcp.v1.McpAgentService.StartService:output_type -> mcp.v1.StartServiceResponse
11, // 49: mcp.v1.McpAgentService.RestartService:output_type -> mcp.v1.RestartServiceResponse
15, // 50: mcp.v1.McpAgentService.SyncDesiredState:output_type -> mcp.v1.SyncDesiredStateResponse
20, // 51: mcp.v1.McpAgentService.ListServices:output_type -> mcp.v1.ListServicesResponse
24, // 52: mcp.v1.McpAgentService.GetServiceStatus:output_type -> mcp.v1.GetServiceStatusResponse
26, // 53: mcp.v1.McpAgentService.LiveCheck:output_type -> mcp.v1.LiveCheckResponse
29, // 54: mcp.v1.McpAgentService.AdoptContainers:output_type -> mcp.v1.AdoptContainersResponse
37, // 55: mcp.v1.McpAgentService.PurgeComponent:output_type -> mcp.v1.PurgeResponse
31, // 56: mcp.v1.McpAgentService.PushFile:output_type -> mcp.v1.PushFileResponse
33, // 57: mcp.v1.McpAgentService.PullFile:output_type -> mcp.v1.PullFileResponse
35, // 58: mcp.v1.McpAgentService.NodeStatus:output_type -> mcp.v1.NodeStatusResponse
44, // 59: mcp.v1.McpAgentService.ListDNSRecords:output_type -> mcp.v1.ListDNSRecordsResponse
48, // 60: mcp.v1.McpAgentService.ListProxyRoutes:output_type -> mcp.v1.ListProxyRoutesResponse
50, // 61: mcp.v1.McpAgentService.AddProxyRoute:output_type -> mcp.v1.AddProxyRouteResponse
52, // 62: mcp.v1.McpAgentService.RemoveProxyRoute:output_type -> mcp.v1.RemoveProxyRouteResponse
40, // 63: mcp.v1.McpAgentService.Logs:output_type -> mcp.v1.LogsResponse
45, // [45:64] is the sub-list for method output_type
26, // [26:45] is the sub-list for method input_type
26, // [26:26] is the sub-list for extension type_name
26, // [26:26] is the sub-list for extension extendee
0, // [0:26] is the sub-list for field type_name
@@ -3163,7 +3385,7 @@ func file_proto_mcp_v1_mcp_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_mcp_v1_mcp_proto_rawDesc), len(file_proto_mcp_v1_mcp_proto_rawDesc)),
NumEnums: 0,
NumMessages: 49,
NumMessages: 53,
NumExtensions: 0,
NumServices: 1,
},

View File

@@ -35,6 +35,8 @@ const (
McpAgentService_NodeStatus_FullMethodName = "/mcp.v1.McpAgentService/NodeStatus"
McpAgentService_ListDNSRecords_FullMethodName = "/mcp.v1.McpAgentService/ListDNSRecords"
McpAgentService_ListProxyRoutes_FullMethodName = "/mcp.v1.McpAgentService/ListProxyRoutes"
McpAgentService_AddProxyRoute_FullMethodName = "/mcp.v1.McpAgentService/AddProxyRoute"
McpAgentService_RemoveProxyRoute_FullMethodName = "/mcp.v1.McpAgentService/RemoveProxyRoute"
McpAgentService_Logs_FullMethodName = "/mcp.v1.McpAgentService/Logs"
)
@@ -67,6 +69,8 @@ type McpAgentServiceClient interface {
ListDNSRecords(ctx context.Context, in *ListDNSRecordsRequest, opts ...grpc.CallOption) (*ListDNSRecordsResponse, error)
// Proxy routes (query mc-proxy)
ListProxyRoutes(ctx context.Context, in *ListProxyRoutesRequest, opts ...grpc.CallOption) (*ListProxyRoutesResponse, error)
AddProxyRoute(ctx context.Context, in *AddProxyRouteRequest, opts ...grpc.CallOption) (*AddProxyRouteResponse, error)
RemoveProxyRoute(ctx context.Context, in *RemoveProxyRouteRequest, opts ...grpc.CallOption) (*RemoveProxyRouteResponse, error)
// Logs
Logs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogsResponse], error)
}
@@ -239,6 +243,26 @@ func (c *mcpAgentServiceClient) ListProxyRoutes(ctx context.Context, in *ListPro
return out, nil
}
func (c *mcpAgentServiceClient) AddProxyRoute(ctx context.Context, in *AddProxyRouteRequest, opts ...grpc.CallOption) (*AddProxyRouteResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(AddProxyRouteResponse)
err := c.cc.Invoke(ctx, McpAgentService_AddProxyRoute_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *mcpAgentServiceClient) RemoveProxyRoute(ctx context.Context, in *RemoveProxyRouteRequest, opts ...grpc.CallOption) (*RemoveProxyRouteResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(RemoveProxyRouteResponse)
err := c.cc.Invoke(ctx, McpAgentService_RemoveProxyRoute_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *mcpAgentServiceClient) Logs(ctx context.Context, in *LogsRequest, opts ...grpc.CallOption) (grpc.ServerStreamingClient[LogsResponse], error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
stream, err := c.cc.NewStream(ctx, &McpAgentService_ServiceDesc.Streams[0], McpAgentService_Logs_FullMethodName, cOpts...)
@@ -287,6 +311,8 @@ type McpAgentServiceServer interface {
ListDNSRecords(context.Context, *ListDNSRecordsRequest) (*ListDNSRecordsResponse, error)
// Proxy routes (query mc-proxy)
ListProxyRoutes(context.Context, *ListProxyRoutesRequest) (*ListProxyRoutesResponse, error)
AddProxyRoute(context.Context, *AddProxyRouteRequest) (*AddProxyRouteResponse, error)
RemoveProxyRoute(context.Context, *RemoveProxyRouteRequest) (*RemoveProxyRouteResponse, error)
// Logs
Logs(*LogsRequest, grpc.ServerStreamingServer[LogsResponse]) error
mustEmbedUnimplementedMcpAgentServiceServer()
@@ -347,6 +373,12 @@ func (UnimplementedMcpAgentServiceServer) ListDNSRecords(context.Context, *ListD
func (UnimplementedMcpAgentServiceServer) ListProxyRoutes(context.Context, *ListProxyRoutesRequest) (*ListProxyRoutesResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListProxyRoutes not implemented")
}
func (UnimplementedMcpAgentServiceServer) AddProxyRoute(context.Context, *AddProxyRouteRequest) (*AddProxyRouteResponse, error) {
return nil, status.Error(codes.Unimplemented, "method AddProxyRoute not implemented")
}
func (UnimplementedMcpAgentServiceServer) RemoveProxyRoute(context.Context, *RemoveProxyRouteRequest) (*RemoveProxyRouteResponse, error) {
return nil, status.Error(codes.Unimplemented, "method RemoveProxyRoute not implemented")
}
func (UnimplementedMcpAgentServiceServer) Logs(*LogsRequest, grpc.ServerStreamingServer[LogsResponse]) error {
return status.Error(codes.Unimplemented, "method Logs not implemented")
}
@@ -659,6 +691,42 @@ func _McpAgentService_ListProxyRoutes_Handler(srv interface{}, ctx context.Conte
return interceptor(ctx, in, info, handler)
}
func _McpAgentService_AddProxyRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AddProxyRouteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(McpAgentServiceServer).AddProxyRoute(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: McpAgentService_AddProxyRoute_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(McpAgentServiceServer).AddProxyRoute(ctx, req.(*AddProxyRouteRequest))
}
return interceptor(ctx, in, info, handler)
}
func _McpAgentService_RemoveProxyRoute_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RemoveProxyRouteRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(McpAgentServiceServer).RemoveProxyRoute(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: McpAgentService_RemoveProxyRoute_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(McpAgentServiceServer).RemoveProxyRoute(ctx, req.(*RemoveProxyRouteRequest))
}
return interceptor(ctx, in, info, handler)
}
func _McpAgentService_Logs_Handler(srv interface{}, stream grpc.ServerStream) error {
m := new(LogsRequest)
if err := stream.RecvMsg(m); err != nil {
@@ -741,6 +809,14 @@ var McpAgentService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListProxyRoutes",
Handler: _McpAgentService_ListProxyRoutes_Handler,
},
{
MethodName: "AddProxyRoute",
Handler: _McpAgentService_AddProxyRoute_Handler,
},
{
MethodName: "RemoveProxyRoute",
Handler: _McpAgentService_RemoveProxyRoute_Handler,
},
},
Streams: []grpc.StreamDesc{
{

View File

@@ -134,7 +134,8 @@ func (a *Agent) deployComponent(ctx context.Context, serviceName string, cs *mcp
Error: fmt.Sprintf("allocate route ports: %v", err),
}
}
runSpec.Ports = ports
// Merge explicit ports from the spec with route-allocated ports.
runSpec.Ports = append(cs.GetPorts(), ports...)
runSpec.Env = append(runSpec.Env, env...)
} else {
// Legacy: use ports directly from the spec.

View File

@@ -56,6 +56,22 @@ func (p *ProxyRouter) GetStatus(ctx context.Context) (*mcproxy.Status, error) {
return p.client.GetStatus(ctx)
}
// AddRoute adds a single route to mc-proxy.
func (p *ProxyRouter) AddRoute(ctx context.Context, listenerAddr string, route mcproxy.Route) error {
if p == nil {
return fmt.Errorf("mc-proxy not configured")
}
return p.client.AddRoute(ctx, listenerAddr, route)
}
// RemoveRoute removes a single route from mc-proxy.
func (p *ProxyRouter) RemoveRoute(ctx context.Context, listenerAddr, hostname string) error {
if p == nil {
return fmt.Errorf("mc-proxy not configured")
}
return p.client.RemoveRoute(ctx, listenerAddr, hostname)
}
// RegisterRoutes registers all routes for a service component with mc-proxy.
// It uses the assigned host ports from the registry.
func (p *ProxyRouter) RegisterRoutes(ctx context.Context, serviceName string, routes []registry.Route, hostPorts map[string]int) error {

View File

@@ -4,7 +4,10 @@ import (
"context"
"fmt"
"git.wntrmute.dev/mc/mc-proxy/client/mcproxy"
mcpv1 "git.wntrmute.dev/mc/mcp/gen/mcp/v1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
)
@@ -44,3 +47,65 @@ func (a *Agent) ListProxyRoutes(ctx context.Context, _ *mcpv1.ListProxyRoutesReq
return resp, nil
}
// AddProxyRoute adds a route to mc-proxy.
func (a *Agent) AddProxyRoute(ctx context.Context, req *mcpv1.AddProxyRouteRequest) (*mcpv1.AddProxyRouteResponse, error) {
if req.GetListenerAddr() == "" {
return nil, status.Error(codes.InvalidArgument, "listener_addr is required")
}
if req.GetHostname() == "" {
return nil, status.Error(codes.InvalidArgument, "hostname is required")
}
if req.GetBackend() == "" {
return nil, status.Error(codes.InvalidArgument, "backend is required")
}
if a.Proxy == nil {
return nil, status.Error(codes.FailedPrecondition, "mc-proxy not configured")
}
route := mcproxy.Route{
Hostname: req.GetHostname(),
Backend: req.GetBackend(),
Mode: req.GetMode(),
BackendTLS: req.GetBackendTls(),
}
if err := a.Proxy.AddRoute(ctx, req.GetListenerAddr(), route); err != nil {
return nil, fmt.Errorf("add route: %w", err)
}
a.Logger.Info("route added",
"listener", req.GetListenerAddr(),
"hostname", req.GetHostname(),
"backend", req.GetBackend(),
"mode", req.GetMode(),
)
return &mcpv1.AddProxyRouteResponse{}, nil
}
// RemoveProxyRoute removes a route from mc-proxy.
func (a *Agent) RemoveProxyRoute(ctx context.Context, req *mcpv1.RemoveProxyRouteRequest) (*mcpv1.RemoveProxyRouteResponse, error) {
if req.GetListenerAddr() == "" {
return nil, status.Error(codes.InvalidArgument, "listener_addr is required")
}
if req.GetHostname() == "" {
return nil, status.Error(codes.InvalidArgument, "hostname is required")
}
if a.Proxy == nil {
return nil, status.Error(codes.FailedPrecondition, "mc-proxy not configured")
}
if err := a.Proxy.RemoveRoute(ctx, req.GetListenerAddr(), req.GetHostname()); err != nil {
return nil, fmt.Errorf("remove route: %w", err)
}
a.Logger.Info("route removed",
"listener", req.GetListenerAddr(),
"hostname", req.GetHostname(),
)
return &mcpv1.RemoveProxyRouteResponse{}, nil
}

View File

@@ -39,6 +39,8 @@ service McpAgentService {
// Proxy routes (query mc-proxy)
rpc ListProxyRoutes(ListProxyRoutesRequest) returns (ListProxyRoutesResponse);
rpc AddProxyRoute(AddProxyRouteRequest) returns (AddProxyRouteResponse);
rpc RemoveProxyRoute(RemoveProxyRouteRequest) returns (RemoveProxyRouteResponse);
// Logs
rpc Logs(LogsRequest) returns (stream LogsResponse);
@@ -353,3 +355,20 @@ message ListProxyRoutesResponse {
google.protobuf.Timestamp started_at = 3;
repeated ProxyListenerInfo listeners = 4;
}
message AddProxyRouteRequest {
string listener_addr = 1; // e.g. ":443"
string hostname = 2;
string backend = 3;
string mode = 4; // "l4" or "l7"
bool backend_tls = 5;
}
message AddProxyRouteResponse {}
message RemoveProxyRouteRequest {
string listener_addr = 1; // e.g. ":443"
string hostname = 2;
}
message RemoveProxyRouteResponse {}