@@ -3,46 +3,52 @@ package cmd
33import (
44 "fmt"
55 "os"
6- "path/filepath"
6+ "os/exec"
7+ "strings"
8+
9+ "github.com/kagent-dev/kmcp/pkg/manifest"
10+ "github.com/stoewer/go-strcase"
711
812 "github.com/kagent-dev/kmcp/pkg/build"
913 "github.com/spf13/cobra"
1014)
1115
1216var buildCmd = & cobra.Command {
1317 Use : "build" ,
14- Short : "Build MCP server" ,
18+ Short : "Build MCP server as a Docker image " ,
1519 Long : `Build an MCP server from the current project.
1620
1721This command will detect the project type and build the appropriate
18- MCP server package or Docker image.
22+ MCP server Docker image.
1923
2024Examples:
21- kmcp build # Build from current directory
22- kmcp build --project-dir /path/to/project # Build from specific directory
23- kmcp build --docker --project-dir ./my-project # Build Docker image from specific directory` ,
25+ kmcp build # Build Docker image from current directory
26+ kmcp build --project-dir ./my-project # Build Docker image from specific directory` ,
2427 RunE : runBuild ,
2528}
2629
2730var (
28- buildDocker bool
29- buildOutput string
30- buildTag string
31- buildPlatform string
32- buildDir string
31+ buildTag string
32+ buildPush bool
33+ buildKindLoad bool
34+ buildDir string
35+ buildPlatform string
36+ buildKindLoadCluster string
3337)
3438
3539func init () {
3640 rootCmd .AddCommand (buildCmd )
3741
38- buildCmd .Flags ().BoolVar (& buildDocker , "docker" , false , "Build Docker image" )
39- buildCmd .Flags ().StringVarP (& buildOutput , "output" , "o" , "" , "Output directory or image name" )
40- buildCmd .Flags ().StringVarP (& buildTag , "tag" , "t" , "" , "Docker image tag" )
41- buildCmd .Flags ().StringVar (& buildPlatform , "platform" , "" , "Target platform (e.g., linux/amd64,linux/arm64)" )
42+ buildCmd .Flags ().StringVarP (& buildTag , "tag" , "t" , "" , "Docker image tag (alias for --output)" )
43+ buildCmd .Flags ().BoolVar (& buildPush , "push" , false , "Push Docker image to registry" )
44+ buildCmd .Flags ().BoolVar (& buildKindLoad , "kind-load" , false , "Load image into kind cluster (requires kind)" )
45+ buildCmd .Flags ().StringVar (& buildKindLoadCluster , "kind-load-cluster" , "" ,
46+ "Name of the kind cluster to load image into (default: current cluster)" )
4247 buildCmd .Flags ().StringVarP (& buildDir , "project-dir" , "d" , "" , "Build directory (default: current directory)" )
48+ buildCmd .Flags ().StringVar (& buildPlatform , "platform" , "" , "Target platform (e.g., linux/amd64,linux/arm64)" )
4349}
4450
45- func runBuild (_ * cobra.Command , _ []string ) error {
51+ func runBuild (cmd * cobra.Command , args []string ) error {
4652 // Determine build directory
4753 buildDirectory := buildDir
4854 if buildDirectory == "" {
@@ -51,59 +57,93 @@ func runBuild(_ *cobra.Command, _ []string) error {
5157 if err != nil {
5258 return fmt .Errorf ("failed to get current directory: %w" , err )
5359 }
54- } else {
55- // Convert relative path to absolute path
56- if ! filepath .IsAbs (buildDirectory ) {
57- cwd , err := os .Getwd ()
58- if err != nil {
59- return fmt .Errorf ("failed to get current directory: %w" , err )
60- }
61- buildDirectory = filepath .Join (cwd , buildDirectory )
62- }
6360 }
6461
65- if verbose {
66- fmt .Printf ("Building MCP server in: %s\n " , buildDirectory )
67- }
62+ imageName := buildTag
63+ if imageName == "" {
64+ // Load project manifest
65+ manifestManager := manifest .NewManager (buildDirectory )
66+ if ! manifestManager .Exists () {
67+ return fmt .Errorf (
68+ "kmcp.yaml not found in %s. Run 'kmcp init' first or specify a valid path with --project-dir" ,
69+ buildDirectory ,
70+ )
71+ }
6872
69- // Check if this is a valid MCP project
70- if err := validateMCPProject (buildDirectory ); err != nil {
71- return fmt .Errorf ("invalid MCP project: %w" , err )
73+ projectManifest , err := manifestManager .Load ()
74+ if err != nil {
75+ return fmt .Errorf ("failed to load project manifest: %w" , err )
76+ }
77+
78+ version := projectManifest .Version
79+ if version == "" {
80+ version = "latest"
81+ }
82+ imageName = fmt .Sprintf ("%s:%s" , strcase .KebabCase (projectManifest .Name ), version )
7283 }
7384
74- // Create build options
85+ // Execute build
86+ builder := build .New ()
7587 opts := build.Options {
7688 ProjectDir : buildDirectory ,
77- Docker : buildDocker ,
78- Output : buildOutput ,
79- Tag : buildTag ,
89+ Tag : imageName ,
8090 Platform : buildPlatform ,
8191 Verbose : verbose ,
8292 }
8393
84- // Execute build
85- builder := build .New ()
86- return builder .Build (opts )
87- }
88-
89- // validateMCPProject checks if the current directory contains a valid MCP project
90- func validateMCPProject (dir string ) error {
91- // Check for common MCP project indicators
92- indicators := []string {
93- "pyproject.toml" , // Python projects
94- "package.json" , // Node.js projects
95- "go.mod" , // Go projects
96- ".mcpbuilder.yaml" , // Our project config
94+ if err := builder .Build (opts ); err != nil {
95+ return fmt .Errorf ("build failed: %w" , err )
9796 }
9897
99- for _ , indicator := range indicators {
100- if _ , err := os .Stat (filepath .Join (dir , indicator )); err == nil {
101- if verbose {
102- fmt .Printf ("Detected project file: %s\n " , indicator )
98+ if buildPush {
99+ fmt .Printf ("Pushing Docker image %s...\n " , imageName )
100+ if err := runDocker ("push" , imageName ); err != nil {
101+ return fmt .Errorf ("docker push failed: %w" , err )
102+ }
103+ fmt .Printf ("✅ Docker image pushed successfully\n " )
104+ }
105+ if buildKindLoad || buildKindLoadCluster != "" {
106+ fmt .Printf ("Loading Docker image %s into kind cluster...\n " , imageName )
107+ kindArgs := []string {"load" , "docker-image" , imageName }
108+ clusterName := buildKindLoadCluster
109+ if clusterName == "" {
110+ var err error
111+ clusterName , err = getCurrentKindClusterName ()
112+ if err != nil {
113+ if verbose {
114+ fmt .Printf ("could not detect kind cluster name: %v, using default\n " , err )
115+ }
116+ clusterName = "kind" // default to kind cluster
103117 }
104- return nil
105118 }
119+
120+ kindArgs = append (kindArgs , "--name" , clusterName )
121+
122+ if err := runKind (kindArgs ... ); err != nil {
123+ return fmt .Errorf ("kind load failed: %w" , err )
124+ }
125+ fmt .Printf ("✅ Docker image loaded into kind cluster %s\n " , clusterName )
106126 }
107127
108- return fmt .Errorf ("no MCP project detected. Expected one of: %v" , indicators )
128+ return nil
129+ }
130+
131+ func runDocker (args ... string ) error {
132+ if verbose {
133+ fmt .Printf ("Running: docker %s\n " , strings .Join (args , " " ))
134+ }
135+ cmd := exec .Command ("docker" , args ... )
136+ cmd .Stdout = os .Stdout
137+ cmd .Stderr = os .Stderr
138+ return cmd .Run ()
139+ }
140+
141+ func runKind (args ... string ) error {
142+ if verbose {
143+ fmt .Printf ("Running: kind %s\n " , strings .Join (args , " " ))
144+ }
145+ cmd := exec .Command ("kind" , args ... )
146+ cmd .Stdout = os .Stdout
147+ cmd .Stderr = os .Stderr
148+ return cmd .Run ()
109149}
0 commit comments