Skip to content

Commit 506f43a

Browse files
authored
An Stdio based MCP server is started that understands all the commands that eksctl provides, interact with EKS via the LLM in natural language and all the eksctl CLI commands are made available as tools
1 parent ce8fa05 commit 506f43a

File tree

14 files changed

+1604
-6
lines changed

14 files changed

+1604
-6
lines changed

cmd/eksctl/main.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@ import (
77

88
"github.com/kris-nova/logger"
99
"github.com/spf13/cobra"
10-
"github.com/weaveworks/eksctl/pkg/ctl/deregister"
11-
"github.com/weaveworks/eksctl/pkg/ctl/register"
1210

1311
"github.com/weaveworks/eksctl/pkg/actions/anywhere"
1412
"github.com/weaveworks/eksctl/pkg/ctl/associate"
1513
"github.com/weaveworks/eksctl/pkg/ctl/cmdutils"
1614
"github.com/weaveworks/eksctl/pkg/ctl/completion"
1715
"github.com/weaveworks/eksctl/pkg/ctl/create"
1816
"github.com/weaveworks/eksctl/pkg/ctl/delete"
17+
"github.com/weaveworks/eksctl/pkg/ctl/deregister"
1918
"github.com/weaveworks/eksctl/pkg/ctl/disassociate"
2019
"github.com/weaveworks/eksctl/pkg/ctl/drain"
2120
"github.com/weaveworks/eksctl/pkg/ctl/enable"
2221
"github.com/weaveworks/eksctl/pkg/ctl/get"
22+
"github.com/weaveworks/eksctl/pkg/ctl/mcp"
23+
"github.com/weaveworks/eksctl/pkg/ctl/misc"
24+
"github.com/weaveworks/eksctl/pkg/ctl/register"
2325
"github.com/weaveworks/eksctl/pkg/ctl/scale"
2426
"github.com/weaveworks/eksctl/pkg/ctl/set"
2527
"github.com/weaveworks/eksctl/pkg/ctl/unset"
@@ -45,11 +47,11 @@ func addCommands(rootCmd *cobra.Command, flagGrouping *cmdutils.FlagGrouping) {
4547
rootCmd.AddCommand(deregister.Command(flagGrouping))
4648
rootCmd.AddCommand(utils.Command(flagGrouping))
4749
rootCmd.AddCommand(completion.Command(rootCmd))
50+
rootCmd.AddCommand(mcp.Command(rootCmd))
4851
//Ensures "eksctl --help" presents eksctl anywhere as a command, but adds no subcommands since we invoke the binary.
4952
rootCmd.AddCommand(cmdutils.NewVerbCmd("anywhere", "EKS anywhere", ""))
5053

51-
cmdutils.AddResourceCmd(flagGrouping, rootCmd, infoCmd)
52-
cmdutils.AddResourceCmd(flagGrouping, rootCmd, versionCmd)
54+
misc.Command(flagGrouping, rootCmd)
5355
}
5456

5557
func main() {
@@ -117,6 +119,10 @@ func checkCommand(rootCmd *cobra.Command) {
117119
if cmd.RunE != nil {
118120
continue
119121
}
122+
// mcp command does not need a resource type
123+
if cmd.Name() == "mcp" {
124+
continue
125+
}
120126
cmd.RunE = func(c *cobra.Command, args []string) error {
121127
var e error
122128
if len(args) == 0 {

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ require (
4747
github.com/kris-nova/lolgopher v0.0.0-20210112022122-73f0047e8b65
4848
github.com/kubicorn/kubicorn v0.0.0-20191114212505-a2c64ce430b9
4949
github.com/lithammer/dedent v1.1.0
50+
github.com/mark3labs/mcp-go v0.31.0
5051
github.com/maxbrunsfeld/counterfeiter/v6 v6.11.2
5152
github.com/onsi/ginkgo v1.16.5
5253
github.com/onsi/ginkgo/v2 v2.23.4
@@ -397,6 +398,7 @@ require (
397398
github.com/yagipy/maintidx v1.0.0 // indirect
398399
github.com/yeya24/promlinter v0.3.0 // indirect
399400
github.com/ykadowak/zerologlint v0.1.5 // indirect
401+
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
400402
gitlab.com/bosi/decorder v0.4.2 // indirect
401403
go-simpler.org/musttag v0.13.0 // indirect
402404
go-simpler.org/sloglint v0.9.0 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,8 @@ github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s
668668
github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE=
669669
github.com/maratori/testpackage v1.1.1 h1:S58XVV5AD7HADMmD0fNnziNHqKvSdDuEKdPD1rNTU04=
670670
github.com/maratori/testpackage v1.1.1/go.mod h1:s4gRK/ym6AMrqpOa/kEbQTV4Q4jb7WeLZzVhVVVOQMc=
671+
github.com/mark3labs/mcp-go v0.31.0 h1:4UxSV8aM770OPmTvaVe/b1rA2oZAjBMhGBfUgOGut+4=
672+
github.com/mark3labs/mcp-go v0.31.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
671673
github.com/matoous/godox v1.1.0 h1:W5mqwbyWrwZv6OQ5Z1a/DHGMOvXYCBP3+Ht7KMoJhq4=
672674
github.com/matoous/godox v1.1.0/go.mod h1:jgE/3fUXiTurkdHOLT5WEkThTSuE7yxHv5iWPa80afs=
673675
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
@@ -1059,6 +1061,8 @@ github.com/yeya24/promlinter v0.3.0 h1:JVDbMp08lVCP7Y6NP3qHroGAO6z2yGKQtS5Jsjqto
10591061
github.com/yeya24/promlinter v0.3.0/go.mod h1:cDfJQQYv9uYciW60QT0eeHlFodotkYZlL+YcPQN+mW4=
10601062
github.com/ykadowak/zerologlint v0.1.5 h1:Gy/fMz1dFQN9JZTPjv1hxEk+sRWm05row04Yoolgdiw=
10611063
github.com/ykadowak/zerologlint v0.1.5/go.mod h1:KaUskqF3e/v59oPmdq1U1DnKcuHokl2/K1U4pmIELKg=
1064+
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
1065+
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
10621066
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
10631067
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
10641068
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

pkg/ctl/mcp/README.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# eksctl MCP (Model Context Protocol) Package
2+
3+
## Overview
4+
5+
The `pkg/ctl/mcp` package implements a [Model Context Protocol (MCP)](https://github.com/modelcontextprotocol) server for eksctl. This server enables AI assistants like Amazon Q to interact with eksctl functionality directly through a standardized protocol, allowing for seamless integration of eksctl commands into AI-powered workflows.
6+
7+
## What is MCP?
8+
9+
Model Context Protocol (MCP) is an open protocol that standardizes how applications provide context and tools to Large Language Models (LLMs). It enables AI assistants to:
10+
11+
1. Discover available tools and their capabilities
12+
2. Execute commands and receive structured responses
13+
3. Provide contextual information to enhance AI interactions
14+
15+
## How the eksctl MCP Server Works
16+
17+
The eksctl MCP server exposes all eksctl commands as tools that can be invoked by AI assistants. The implementation consists of several key components:
18+
19+
### Core Components
20+
21+
1. **`mcp.go`**: Entry point that defines the `mcp` command and starts the MCP server
22+
2. **`server.go`**: Creates and configures the MCP server with all eksctl commands
23+
3. **`tools.go`**: Handles the registration of eksctl commands as MCP tools
24+
4. **`types.go`**: Defines types and interfaces used by the MCP implementation
25+
26+
### Implementation Details
27+
28+
- The server uses the `mcp-go` library to implement the Model Context Protocol
29+
- It dynamically registers all eksctl commands (create, get, delete, etc.) as MCP tools
30+
- Each command's help text, flags, and parameters are exposed through the protocol
31+
- The server runs as a stdio server, communicating through standard input/output
32+
33+
### Command Registration Process
34+
35+
1. The server recursively traverses the eksctl command tree
36+
2. For each command, it extracts:
37+
- Command description and usage information
38+
- Available flags and their descriptions
39+
- Required and optional parameters
40+
41+
## Using the eksctl MCP Server with Amazon Q Chat
42+
43+
To use the eksctl MCP server with Amazon Q Chat, you need to register it in the Amazon Q configuration file.
44+
45+
Create or edit the file at `$HOME/.aws/amazonq/mcp.json` with the following content:
46+
47+
```json
48+
{
49+
"mcpServers": {
50+
"eks-tools": {
51+
"command": "eksctl",
52+
"args": [
53+
"mcp"
54+
]
55+
}
56+
}
57+
}
58+
```
59+
60+
This configuration tells Amazon Q Chat to:
61+
1. Register a server named "eks-tools"
62+
2. Use the `eksctl mcp` command to start the MCP server
63+
3. Make all eksctl commands available as tools with the prefix `eks___`
64+
65+
## Benefits
66+
67+
- **Seamless Integration**: AI assistants can execute eksctl commands directly
68+
- **Structured Responses**: Commands return structured data that can be parsed by AI models
69+
- **Discoverability**: AI assistants can discover available commands and their parameters
70+
- **Context-Aware**: Provides rich context about EKS clusters and resources
71+
72+
## Development
73+
74+
When extending the MCP server functionality:
75+
76+
1. Add new command registrations in `server.go` if new eksctl commands are created
77+
2. Extend type definitions in `types.go` as needed
78+
3. Modify `tools.go` if changes to tool registration logic are required
79+
80+
## Example Usage in Amazon Q Chat
81+
82+
Once configured, users can interact with eksctl through Amazon Q Chat:
83+
84+
```
85+
User: What EKS clusters do I have?
86+
Amazon Q: Let me check your EKS clusters.
87+
[Amazon Q executes the eksctl get cluster command and displays the results]
88+
89+
User: Create a new EKS cluster
90+
Amazon Q: [Guides the user through cluster creation using eksctl commands]
91+
```
92+
93+
The MCP server enables these interactions by providing Amazon Q with the ability to execute eksctl commands and interpret their results.
94+
95+
## Quick test for MCP server
96+
To quickly test the MCP server, you can run the following command in your terminal:
97+
98+
```bash
99+
echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | eksctl mcp | jq
100+
```
101+
102+
## Recommendations
103+
- Monitor background operations for long-running commands. Commands such as cluster creations have a 45-second timeout for command responses but the processes continue in background.

pkg/ctl/mcp/mcp.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Package mcp implements the Model Context Protocol (MCP) server functionality for eksctl
2+
// MCP allows eksctl to be used as a tool provider for AI assistants
3+
package mcp
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"os"
9+
10+
"github.com/mark3labs/mcp-go/server"
11+
"github.com/spf13/cobra"
12+
13+
"github.com/weaveworks/eksctl/pkg/version"
14+
)
15+
16+
// Command creates the `mcp` commands
17+
// This command starts an MCP server that provides eksctl functionality through the Model Context Protocol
18+
func Command(rootCommand *cobra.Command) *cobra.Command {
19+
cmd := &cobra.Command{
20+
Use: "mcp",
21+
Short: "Start an MCP (Model Context Protocol) server",
22+
Long: "Start an MCP server that provides eksctl functionality through the Model Context Protocol",
23+
Run: func(_ *cobra.Command, _ []string) {
24+
startMCPServer(rootCommand)
25+
},
26+
Hidden: true, // Hide this command from normal usage
27+
}
28+
29+
return cmd
30+
}
31+
32+
// startMCPServer initializes and starts the MCP server
33+
// It creates an eksctl MCP server and connects it to stdin/stdout for communication
34+
func startMCPServer(rootCommand *cobra.Command) {
35+
// Create the MCP server
36+
mcpServer, err := newEksctlMCPServer(rootCommand, version.GetVersion())
37+
if err != nil {
38+
fmt.Fprintf(os.Stderr, "Error creating MCP server: %v\n", err)
39+
os.Exit(1)
40+
}
41+
42+
// Create a stdio server
43+
stdioServer := server.NewStdioServer(mcpServer)
44+
45+
// Start the server
46+
if err := stdioServer.Listen(context.Background(), os.Stdin, os.Stdout); err != nil {
47+
fmt.Fprintf(os.Stderr, "Error starting MCP server: %v\n", err)
48+
os.Exit(1)
49+
}
50+
}
51+
52+
// newEksctlMCPServer creates and configures an MCP server for eksctl
53+
// It sets up the command structure and registers all eksctl commands as MCP tools
54+
func newEksctlMCPServer(rootCmd *cobra.Command, version string) (*server.MCPServer, error) {
55+
56+
// Create a new MCP server with the specified name and version
57+
s := server.NewMCPServer("eksctl", version, server.WithInstructions("MCP server for eksctl"))
58+
59+
// Register all eksctl commands as MCP tools
60+
if err := registerTools(s, rootCmd); err != nil {
61+
return nil, err
62+
}
63+
64+
return s, nil
65+
}

pkg/ctl/mcp/mcp_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package mcp
2+
3+
import (
4+
"testing"
5+
6+
"github.com/spf13/cobra"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestNewEksctlMCPServer(t *testing.T) {
12+
// Create a simple root command for testing
13+
rootCmd := &cobra.Command{
14+
Use: "eksctl",
15+
Short: "The official CLI for Amazon EKS",
16+
}
17+
18+
// Add a test subcommand
19+
testCmd := &cobra.Command{
20+
Use: "test",
21+
Short: "Test command",
22+
Run: func(cmd *cobra.Command, args []string) {},
23+
}
24+
rootCmd.AddCommand(testCmd)
25+
26+
// Create the MCP server
27+
mcpServer, err := newEksctlMCPServer(rootCmd, "test-version")
28+
29+
// Verify server creation
30+
assert.NoError(t, err)
31+
assert.NotNil(t, mcpServer)
32+
33+
// We can't directly access name and version fields, but we can verify the server was created
34+
require.NotNil(t, mcpServer)
35+
}
36+
37+
func TestCommand(t *testing.T) {
38+
// Create a root command
39+
rootCmd := &cobra.Command{
40+
Use: "eksctl",
41+
Short: "The official CLI for Amazon EKS",
42+
}
43+
44+
// Create the MCP command
45+
mcpCmd := Command(rootCmd)
46+
47+
// Verify command properties
48+
assert.Equal(t, "mcp", mcpCmd.Use)
49+
assert.True(t, mcpCmd.Hidden)
50+
assert.NotEmpty(t, mcpCmd.Short)
51+
assert.NotEmpty(t, mcpCmd.Long)
52+
}

pkg/ctl/mcp/misc_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package mcp
2+
3+
import (
4+
"testing"
5+
6+
"github.com/spf13/cobra"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
"github.com/weaveworks/eksctl/pkg/ctl/cmdutils"
10+
"github.com/weaveworks/eksctl/pkg/ctl/misc"
11+
)
12+
13+
// TestMiscCommandRegistration tests that misc commands are properly registered
14+
func TestMiscCommandRegistration(t *testing.T) {
15+
// Create a root command
16+
rootCmd := &cobra.Command{
17+
Use: "eksctl",
18+
Short: "The official CLI for Amazon EKS",
19+
}
20+
21+
// Create a flag grouping for the commands
22+
flagGrouping := cmdutils.NewGrouping()
23+
24+
// Register misc commands to the root command
25+
misc.Command(flagGrouping, rootCmd)
26+
27+
// Create the MCP server with the root command
28+
mcpServer, err := newEksctlMCPServer(rootCmd, "test-version")
29+
require.NoError(t, err)
30+
require.NotNil(t, mcpServer)
31+
32+
// Verify that the version command was registered
33+
versionCmd := findCommandInTest(rootCmd, "version")
34+
assert.NotNil(t, versionCmd, "version command should be registered")
35+
36+
// Verify that the info command was registered
37+
infoCmd := findCommandInTest(rootCmd, "info")
38+
assert.NotNil(t, infoCmd, "info command should be registered")
39+
}
40+
41+
// findCommandInTest recursively searches for a command by name
42+
func findCommandInTest(cmd *cobra.Command, name string) *cobra.Command {
43+
for _, subCmd := range cmd.Commands() {
44+
if subCmd.Name() == name {
45+
return subCmd
46+
}
47+
if found := findCommandInTest(subCmd, name); found != nil {
48+
return found
49+
}
50+
}
51+
return nil
52+
}

0 commit comments

Comments
 (0)