Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import (
"github.com/golgoth31/sreportal/internal/config"
"github.com/golgoth31/sreportal/internal/controller"
portalctrl "github.com/golgoth31/sreportal/internal/controller/portal"
"github.com/golgoth31/sreportal/internal/mcp"
webhookv1alpha1 "github.com/golgoth31/sreportal/internal/webhook/v1alpha1"
"github.com/golgoth31/sreportal/internal/webserver"
// +kubebuilder:scaffold:imports
Expand Down Expand Up @@ -76,6 +77,9 @@ func main() {
var enableHTTP2 bool
var configPath string
var portalNamespace string
var enableMCP bool
var mcpTransport string
var mcpAddr string
var tlsOpts []func(*tls.Config)
flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
Expand All @@ -102,6 +106,12 @@ func main() {
flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.")
flag.BoolVar(&enableHTTP2, "enable-http2", false,
"If set, HTTP/2 will be enabled for the metrics and webhook servers")
flag.BoolVar(&enableMCP, "enable-mcp", false,
"If set, the MCP (Model Context Protocol) server will be enabled for AI assistant integration.")
flag.StringVar(&mcpTransport, "mcp-transport", "stdio",
"The transport to use for the MCP server: 'stdio' or 'sse'.")
flag.StringVar(&mcpAddr, "mcp-bind-address", ":8081",
"The address the MCP SSE server binds to (only used when mcp-transport is 'sse').")
opts := zap.Options{
Development: true,
}
Expand Down Expand Up @@ -326,6 +336,30 @@ func main() {
}
}()

// Start MCP server if enabled
if enableMCP {
mcpServer := mcp.New(mgr.GetClient(), &operatorConfig.GroupMapping)
switch mcpTransport {
case "stdio":
go func() {
setupLog.Info("starting MCP server", "transport", "stdio")
if err := mcpServer.ServeStdio(); err != nil {
setupLog.Error(err, "MCP server error")
}
}()
case "sse":
go func() {
setupLog.Info("starting MCP server", "transport", "sse", "address", mcpAddr)
if err := mcpServer.ServeSSE(mcpAddr); err != nil {
setupLog.Error(err, "MCP server error")
}
}()
default:
setupLog.Error(nil, "unknown MCP transport", "transport", mcpTransport)
os.Exit(1)
}
}

ctx := ctrl.SetupSignalHandler()

setupLog.Info("starting manager")
Expand Down
9 changes: 8 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ go 1.25.3
require (
connectrpc.com/connect v1.19.1
github.com/labstack/echo/v5 v5.0.3
github.com/mark3labs/mcp-go v0.44.0
github.com/onsi/ginkgo/v2 v2.27.2
github.com/onsi/gomega v1.38.2
github.com/stretchr/testify v1.11.1
golang.org/x/net v0.49.0
google.golang.org/protobuf v1.36.10
istio.io/client-go v1.28.0
Expand All @@ -26,8 +28,10 @@ require (
github.com/alecthomas/kingpin/v2 v2.4.0 // indirect
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381 // indirect
Expand Down Expand Up @@ -55,6 +59,7 @@ require (
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
Expand All @@ -72,13 +77,15 @@ require (
github.com/prometheus/procfs v0.17.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/smartystreets/goconvey v1.7.2 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/cobra v1.10.1 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/stoewer/go-strcase v1.3.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/stretchr/testify v1.11.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.37.0 // indirect
Expand Down
16 changes: 16 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
Expand All @@ -93,6 +95,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
Expand Down Expand Up @@ -206,6 +210,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
Expand Down Expand Up @@ -430,6 +436,8 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
Expand Down Expand Up @@ -500,6 +508,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mark3labs/mcp-go v0.44.0 h1:OlYfcVviAnwNN40QZUrrzU0QZjq3En7rCU5X09a/B7I=
github.com/mark3labs/mcp-go v0.44.0/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11 h1:YFh+sjyJTMQSYjKwM4dFKhJPJC/wfo98tPUc17HdoYw=
github.com/martini-contrib/render v0.0.0-20150707142108-ec18f8345a11/go.mod h1:Ah2dBMoxZEqk118as2T4u4fjfXarE0pPnMJaArZQZsI=
Expand Down Expand Up @@ -711,6 +721,8 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
Expand Down Expand Up @@ -771,6 +783,8 @@ github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
Expand All @@ -784,6 +798,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMx
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
Expand Down
134 changes: 134 additions & 0 deletions internal/mcp/get_fqdn_details.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
Copyright 2026.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package mcp

import (
"context"
"encoding/json"
"fmt"
"strings"

"github.com/mark3labs/mcp-go/mcp"

sreportalv1alpha1 "github.com/golgoth31/sreportal/api/v1alpha1"
"github.com/golgoth31/sreportal/internal/adapter"
)

// FQDNDetails represents detailed information about a specific FQDN
type FQDNDetails struct {
Name string `json:"name"`
Source string `json:"source"`
Group string `json:"group"`
Description string `json:"description,omitempty"`
RecordType string `json:"record_type"`
Targets []string `json:"targets"`
Portal string `json:"portal,omitempty"`
Namespace string `json:"namespace,omitempty"`
LastSeen string `json:"last_seen,omitempty"`
DNSResource string `json:"dns_resource,omitempty"`
}

// handleGetFQDNDetails handles the get_fqdn_details tool call
func (s *Server) handleGetFQDNDetails(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// Extract required parameter
fqdn, err := request.RequireString("fqdn")
if err != nil {
return mcp.NewToolResultError("fqdn parameter is required"), nil
}

// Normalize the FQDN for comparison
fqdnLower := strings.ToLower(strings.TrimSuffix(fqdn, "."))

// Search in all DNS resources
var dnsList sreportalv1alpha1.DNSList
if err := s.client.List(ctx, &dnsList); err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to list DNS resources: %v", err)), nil
}

// Look for the FQDN in DNS status
for _, dns := range dnsList.Items {
for _, grp := range dns.Status.Groups {
for _, fqdnStatus := range grp.FQDNs {
fqdnStatusLower := strings.ToLower(strings.TrimSuffix(fqdnStatus.FQDN, "."))
if fqdnStatusLower == fqdnLower {
details := FQDNDetails{
Name: fqdnStatus.FQDN,
Source: grp.Source,
Group: grp.Name,
Description: fqdnStatus.Description,
RecordType: fqdnStatus.RecordType,
Targets: fqdnStatus.Targets,
Portal: dns.Spec.PortalRef,
Namespace: dns.Namespace,
DNSResource: fmt.Sprintf("%s/%s", dns.Namespace, dns.Name),
}
if !fqdnStatus.LastSeen.IsZero() {
details.LastSeen = fqdnStatus.LastSeen.Format("2006-01-02T15:04:05Z07:00")
}

jsonBytes, err := json.MarshalIndent(details, "", " ")
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal details: %v", err)), nil
}

return mcp.NewToolResultText(fmt.Sprintf("FQDN details for '%s':\n\n%s", fqdn, string(jsonBytes))), nil
}
}
}
}

// Also check DNSRecords directly
var dnsRecordList sreportalv1alpha1.DNSRecordList
if err := s.client.List(ctx, &dnsRecordList); err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to list DNSRecord resources: %v", err)), nil
}

var allEndpoints []sreportalv1alpha1.EndpointStatus
for _, rec := range dnsRecordList.Items {
allEndpoints = append(allEndpoints, rec.Status.Endpoints...)
}

if len(allEndpoints) > 0 {
groups := adapter.EndpointStatusToGroups(allEndpoints, s.groupMapping)
for _, grp := range groups {
for _, fqdnStatus := range grp.FQDNs {
fqdnStatusLower := strings.ToLower(strings.TrimSuffix(fqdnStatus.FQDN, "."))
if fqdnStatusLower == fqdnLower {
details := FQDNDetails{
Name: fqdnStatus.FQDN,
Source: grp.Source,
Group: grp.Name,
RecordType: fqdnStatus.RecordType,
Targets: fqdnStatus.Targets,
}
if !fqdnStatus.LastSeen.IsZero() {
details.LastSeen = fqdnStatus.LastSeen.Format("2006-01-02T15:04:05Z07:00")
}

jsonBytes, err := json.MarshalIndent(details, "", " ")
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal details: %v", err)), nil
}

return mcp.NewToolResultText(fmt.Sprintf("FQDN details for '%s':\n\n%s", fqdn, string(jsonBytes))), nil
}
}
}
}

return mcp.NewToolResultText(fmt.Sprintf("FQDN '%s' not found.", fqdn)), nil
}
75 changes: 75 additions & 0 deletions internal/mcp/list_portals.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
Copyright 2026.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package mcp

import (
"context"
"encoding/json"
"fmt"

"github.com/mark3labs/mcp-go/mcp"

sreportalv1alpha1 "github.com/golgoth31/sreportal/api/v1alpha1"
)

// PortalResult represents a portal in the list results
type PortalResult struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Title string `json:"title"`
Main bool `json:"main"`
SubPath string `json:"subPath,omitempty"`
RemoteURL string `json:"remoteUrl,omitempty"`
Ready bool `json:"ready"`
}

// handleListPortals handles the list_portals tool call
func (s *Server) handleListPortals(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// List all Portal resources
var portalList sreportalv1alpha1.PortalList
if err := s.client.List(ctx, &portalList); err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to list Portal resources: %v", err)), nil
}

if len(portalList.Items) == 0 {
return mcp.NewToolResultText("No portals found."), nil
}

// Convert to results
results := make([]PortalResult, 0, len(portalList.Items))
for _, portal := range portalList.Items {
result := PortalResult{
Name: portal.Name,
Namespace: portal.Namespace,
Title: portal.Spec.Title,
Main: portal.Spec.Main,
SubPath: portal.Spec.SubPath,
Ready: portal.Status.Ready,
}
if portal.Spec.Remote != nil {
result.RemoteURL = portal.Spec.Remote.URL
}
results = append(results, result)
}

jsonBytes, err := json.MarshalIndent(results, "", " ")
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal results: %v", err)), nil
}

return mcp.NewToolResultText(fmt.Sprintf("Found %d portal(s):\n\n%s", len(results), string(jsonBytes))), nil
}
Loading
Loading