diff --git a/.env b/.env new file mode 100644 index 0000000..b3ded5d --- /dev/null +++ b/.env @@ -0,0 +1,7 @@ +CYCLOPS_MCP_VERSION=0.0.0 +CYCLOPS_MCP_TRANSPORT= +CYCLOPS_MCP_SSE_ADDRESS= +CYCLOPS_KUBE_CONTEXT= +CYCLOPS_MODULE_NAMESPACE=cyclops +CYCLOPS_HELM_RELEASE_NAMESPACE= +CYCLOPS_MODULE_TARGET_NAMESPACE= diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..98adaf5 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,103 @@ +name: build + +on: + workflow_dispatch: + inputs: + version: + required: true + description: Version to be used as release name and image tagging + +permissions: + contents: write + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - name: Test build + run: | + go build ./cmd/mcp-cyclops + + build-mcp-server: + needs: + - test + runs-on: ubuntu-latest + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64,linux/arm64 + build-args: | + VERSION=${{ github.event.inputs.version }} + push: true + tags: cyclopsui/cyclops-mcp:${{ github.event.inputs.version }} + + update-install-manifest: + needs: + - build-mcp-server + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Extract branch name + shell: bash + run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT + id: extract_branch + - name: update install manifest + run: | + TAG=${{ github.event.inputs.version }} + INSTALL_YAML=$GITHUB_WORKSPACE/install/mcp-server.yaml + + sed -i 's/cyclopsui\/cyclops-mcp\:.*/cyclopsui\/cyclops-mcp\:'$TAG'/' $INSTALL_YAML + + # update file + git fetch origin ${{ steps.extract_branch.outputs.branch }} + git checkout ${{ steps.extract_branch.outputs.branch }} + git config user.name 'github-actions[bot]' + git config user.email 'github-actions[bot]@users.noreply.github.com' + git status + git add $INSTALL_YAML + git commit -m '⚙️ update cyclops to '$TAG + git push origin HEAD:${{ steps.extract_branch.outputs.branch }} + + + release: + needs: + - update-install-manifest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Extract branch name + shell: bash + run: echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT + id: extract_branch + - name: Create release + env: + GH_TOKEN: ${{ github.token }} + run: | + if [ "${{ steps.extract_branch.outputs.branch }}" = "main" ]; then + gh release create ${{ github.event.inputs.version }} \ + --repo="https://github.com/cyclops-ui/cyclops" \ + --title="${{ github.event.inputs.version }}" \ + --generate-notes + else + gh release create ${{ github.event.inputs.version }} \ + --repo="https://github.com/cyclops-ui/cyclops" \ + --title="${{ github.event.inputs.version }}" \ + --generate-notes \ + --target ${{ steps.extract_branch.outputs.branch }}\ + --prerelease + fi diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d02000c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM golang:1.23.8-alpine as build + +WORKDIR /build + +COPY go.mod ./ +RUN go mod download +RUN go mod tidy + +COPY ./ ./ + +RUN go build -o /build/bin ./... + +FROM alpine:3.20.0 + +ARG VERSION +ENV CYCLOPS_MCP_VERSION=$VERSION +ENV CYCLOPS_MCP_TRANSPORT=sse + +WORKDIR /app + +RUN mkdir /app/bin + +COPY --from=build /build/bin/mcp-cyclops bin/mcp-cyclops + +CMD ["bin/mcp-cyclops"] diff --git a/cmd/mcp-cyclops/main.go b/cmd/mcp-cyclops/main.go index 584d480..22db010 100644 --- a/cmd/mcp-cyclops/main.go +++ b/cmd/mcp-cyclops/main.go @@ -1,12 +1,14 @@ package main import ( - "log" + "context" + "fmt" "os" "path/filepath" "github.com/mark3labs/mcp-go/server" + _ "github.com/joho/godotenv/autoload" "sigs.k8s.io/controller-runtime/pkg/log/zap" "github.com/cyclops-ui/cyclops/cyclops-ctrl/pkg/auth" @@ -20,8 +22,14 @@ import ( ) type Config struct { - kubeconfigPath string - kubeContext string + version string + + transport string + address string + + kubeconfigPath string + kubeContext string + moduleNamespace string helmReleaseNamespace string moduleTargetNamespace string @@ -48,7 +56,7 @@ func main() { zap.New(), ) if err != nil { - log.Printf("Failed to create Kubernetes client: %v\n", err) + fmt.Println(fmt.Sprintf("Failed to create Kubernetes client: %v", err)) os.Exit(1) } @@ -66,14 +74,27 @@ func main() { templatesController := templates.NewController(templatesRepo) templatesController.RegisterTemplateStoreTools(s) - if err := server.ServeStdio(s); err != nil { - log.Printf("Server error: %v\n", err) - os.Exit(1) + switch config.transport { + case "stdio": + stdioServer := server.NewStdioServer(s) + if err := stdioServer.Listen(context.Background(), os.Stdin, os.Stdin); err != nil { + panic(err) + } + case "sse": + sseServer := server.NewSSEServer(s) + if err := sseServer.Start(config.address); err != nil { + panic(err) + } + default: + panic("invalid transport type - should be stdio or sse") } } func loadConfig() *Config { config := &Config{ + version: getEnvOrDefault("CYCLOPS_MCP_VERSION", "0.0.0"), + transport: getEnvOrDefault("CYCLOPS_MCP_TRANSPORT", "stdio"), + address: getEnvOrDefault("CYCLOPS_MCP_SSE_ADDRESS", "127.0.0.1:8000"), moduleNamespace: getEnvOrDefault("CYCLOPS_MODULE_NAMESPACE", "cyclops"), helmReleaseNamespace: os.Getenv("CYCLOPS_HELM_RELEASE_NAMESPACE"), moduleTargetNamespace: os.Getenv("CYCLOPS_MODULE_TARGET_NAMESPACE"), diff --git a/go.mod b/go.mod index 96047a1..e69c284 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ module github.com/cyclops-ui/mcp-cyclops go 1.23.8 require ( - github.com/cyclops-ui/cyclops/cyclops-ctrl v0.0.0-20250424103816-0aa958f49c5d + github.com/cyclops-ui/cyclops/cyclops-ctrl v0.0.0-20250425140426-3fe1a1ecab1e + github.com/joho/godotenv v1.5.1 github.com/mark3labs/mcp-go v0.22.0 k8s.io/apiextensions-apiserver v0.32.3 k8s.io/apimachinery v0.32.3 diff --git a/go.sum b/go.sum index 871dd01..ad17d0c 100644 --- a/go.sum +++ b/go.sum @@ -47,13 +47,8 @@ github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyclops-ui/cyclops v0.18.5/go.mod h1:6H1Dk8t18WQFYHUn1aGTTvwcrZGTdAnBh6qGcDRdtNk= -github.com/cyclops-ui/cyclops/cyclops-ctrl v0.0.0-20250422163445-4a15b13ad3a5 h1:WD2f+0cfEC3QXKbUYUW8K8CH673Wezw6Ho+M5RQc5XY= -github.com/cyclops-ui/cyclops/cyclops-ctrl v0.0.0-20250422163445-4a15b13ad3a5/go.mod h1:X9riUJ/32Bs2c3ZK2w1WXxHmZbpn/kh4YaLFICzbPuo= -github.com/cyclops-ui/cyclops/cyclops-ctrl v0.0.0-20250424075329-c9fd7a0fa219 h1:UrXhaTTIjM83uZJvPQ9ZjhbwI3385VoR66fZnjaQjKE= -github.com/cyclops-ui/cyclops/cyclops-ctrl v0.0.0-20250424075329-c9fd7a0fa219/go.mod h1:X9riUJ/32Bs2c3ZK2w1WXxHmZbpn/kh4YaLFICzbPuo= -github.com/cyclops-ui/cyclops/cyclops-ctrl v0.0.0-20250424103816-0aa958f49c5d h1:5P4qblnDB9W0n58v2zdpVJc7GvCQ7LO2X4yry4KX30c= -github.com/cyclops-ui/cyclops/cyclops-ctrl v0.0.0-20250424103816-0aa958f49c5d/go.mod h1:X9riUJ/32Bs2c3ZK2w1WXxHmZbpn/kh4YaLFICzbPuo= +github.com/cyclops-ui/cyclops/cyclops-ctrl v0.0.0-20250425140426-3fe1a1ecab1e h1:H2I/WXA7FBfz6VNFdMt3IB2YZtzPbwzmMELdkRuDuRc= +github.com/cyclops-ui/cyclops/cyclops-ctrl v0.0.0-20250425140426-3fe1a1ecab1e/go.mod h1:X9riUJ/32Bs2c3ZK2w1WXxHmZbpn/kh4YaLFICzbPuo= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -181,6 +176,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= diff --git a/install/mcp-server.yaml b/install/mcp-server.yaml new file mode 100644 index 0000000..6a464d0 --- /dev/null +++ b/install/mcp-server.yaml @@ -0,0 +1,39 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: cyclops-mcp + labels: + app: cyclops-mcp +spec: + replicas: 1 + selector: + matchLabels: + app: cyclops-mcp + template: + metadata: + labels: + app: cyclops-mcp + spec: + containers: + - name: cyclops-mcp + image: cyclopsui/cyclops-mcp:0.0.1 + ports: + - name: http + containerPort: 8000 + protocol: TCP +--- +apiVersion: v1 +kind: Service +metadata: + name: cyclops-mcp + labels: + app: cyclops-mcp +spec: + type: ClusterIP + ports: + - port: 8000 + targetPort: 8000 + protocol: TCP + name: http + selector: + app: cyclops-mcp