Skip to content

Commit de5ea64

Browse files
authored
Merge pull request #1 from arkadeepsen/k8s-layer
Add kubernetes layer for the ovnk mcp server
2 parents f5622ca + c4e2e7f commit de5ea64

File tree

5,580 files changed

+1837556
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

5,580 files changed

+1837556
-0
lines changed

.github/workflows/test.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Test
2+
on:
3+
merge_group:
4+
pull_request:
5+
branches:
6+
- main
7+
jobs:
8+
test:
9+
name: Unit Test
10+
runs-on: ubuntu-24.04
11+
steps:
12+
- name: Checkout code
13+
uses: actions/checkout@v4
14+
- name: Set up Go
15+
uses: actions/setup-go@v5
16+
with:
17+
go-version-file: go.mod
18+
- name: Run unit tests
19+
run: make test

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
_output/

Makefile

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Get the Git repository root directory
2+
GIT_ROOT := $(shell git rev-parse --show-toplevel)
3+
4+
export MCP_SERVER_PATH := $(GIT_ROOT)/_output/ovnk-mcp-server
5+
export KUBECONFIG := $(HOME)/ovn.conf
6+
7+
# CONTAINER_RUNNABLE determines if the tests can be run inside a container. It checks to see if
8+
# podman/docker is installed on the system.
9+
PODMAN ?= $(shell podman -v > /dev/null 2>&1; echo $$?)
10+
ifeq ($(PODMAN), 0)
11+
CONTAINER_RUNTIME?=podman
12+
else
13+
CONTAINER_RUNTIME?=docker
14+
endif
15+
CONTAINER_RUNNABLE ?= $(shell $(CONTAINER_RUNTIME) -v > /dev/null 2>&1; echo $$?)
16+
17+
GOPATH ?= $(shell go env GOPATH)
18+
19+
.PHONY: build
20+
build:
21+
go build -o $(MCP_SERVER_PATH) cmd/ovnk-mcp-server/main.go
22+
23+
.PHONY: clean
24+
clean:
25+
rm -Rf _output/
26+
27+
EXCLUDE_DIRS ?= test/
28+
TEST_PKGS := $$(go list ./... | grep -v $(EXCLUDE_DIRS))
29+
30+
.PHONY: test
31+
test:
32+
go test -v $(TEST_PKGS)
33+
34+
.PHONY: deploy-kind-ovnk
35+
deploy-kind-ovnk:
36+
./hack/deploy-kind-ovnk.sh
37+
38+
.PHONY: undeploy-kind-ovnk
39+
undeploy-kind-ovnk:
40+
./hack/undeploy-kind-ovnk.sh
41+
42+
NVM_VERSION := 0.40.3
43+
NODE_VERSION := 22.20.0
44+
NPM_VERSION := 11.6.1
45+
46+
.PHONY: run-e2e
47+
run-e2e:
48+
./hack/run-e2e.sh $(NVM_VERSION) $(NODE_VERSION) $(NPM_VERSION)
49+
50+
.PHONY: test-e2e
51+
test-e2e: build deploy-kind-ovnk run-e2e undeploy-kind-ovnk
52+
53+
.PHONY: lint
54+
lint:
55+
ifeq ($(CONTAINER_RUNNABLE), 0)
56+
@GOPATH=${GOPATH} ./hack/lint.sh $(CONTAINER_RUNTIME) || { echo "lint failed! Try running 'make lint-fix'"; exit 1; }
57+
else
58+
echo "linter can only be run within a container since it needs a specific golangci-lint version"; exit 1
59+
endif
60+
61+
.PHONY: lint-fix
62+
lint-fix:
63+
ifeq ($(CONTAINER_RUNNABLE), 0)
64+
@GOPATH=${GOPATH} ./hack/lint.sh ${CONTAINER_RUNTIME} fix || { echo "ERROR: lint fix failed! There is a bug that changes file ownership to root \
65+
when this happens. To fix it, simply run 'chown -R <user>:<group> *' from the repo root."; exit 1; }
66+
else
67+
echo "linter can only be run within a container since it needs a specific golangci-lint version"; exit 1
68+
endif

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,46 @@
11
# ovn-kubernetes-mcp
22
Repo hosting the Model Context Protocol Server for troubleshooting OVN-Kubernetes
3+
4+
## How to connect to the MCP Server
5+
6+
For connecting to the MCP server, the following steps are required:
7+
8+
```shell
9+
make build
10+
```
11+
12+
The server currently supports 2 transport modes: `stdio` and `http`.
13+
14+
For `stdio` mode, the server can be run and connected to by using the following configuration in an MCP host (Cursor, Claude, etc.):
15+
16+
```json
17+
{
18+
"mcpServers": {
19+
"ovn-kubernetes": {
20+
"command": "/PATH-TO-THE-LOCAL-GIT-REPO/_output/ovnk-mcp-server",
21+
"args": [
22+
"--kubeconfig",
23+
"/PATH-TO-THE-KUBECONFIG-FILE"
24+
]
25+
}
26+
}
27+
}
28+
```
29+
30+
For `http` mode, the server should be started separately.
31+
32+
```shell
33+
./PATH-TO-THE-LOCAL-GIT-REPO/_output/ovnk-mcp-server --transport http --kubeconfig /PATH-TO-THE-KUBECONFIG-FILE
34+
```
35+
36+
The following configuration should be used in an MCP host (Cursor, Claude, etc.) to connect to the server:
37+
38+
```json
39+
{
40+
"mcpServers": {
41+
"ovn-kubernetes": {
42+
"url": "http://localhost:8080"
43+
}
44+
}
45+
}
46+
```

cmd/ovnk-mcp-server/main.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"flag"
6+
"fmt"
7+
"log"
8+
"net/http"
9+
"os"
10+
"os/signal"
11+
"syscall"
12+
13+
mcp "github.com/modelcontextprotocol/go-sdk/mcp"
14+
kubernetesmcp "github.com/ovn-kubernetes/ovn-kubernetes-mcp/pkg/kubernetes/mcp"
15+
)
16+
17+
type MCPServerConfig struct {
18+
Mode string
19+
Transport string
20+
Port string
21+
Kubernetes kubernetesmcp.Config
22+
}
23+
24+
func main() {
25+
serverCfg := parseFlags()
26+
27+
ovnkMcpServer := mcp.NewServer(
28+
&mcp.Implementation{Name: "ovn-kubernetes"},
29+
&mcp.ServerOptions{HasTools: true},
30+
)
31+
32+
if serverCfg.Mode == "live-cluster" {
33+
k8sMcpServer, err := kubernetesmcp.NewMCPServer(serverCfg.Kubernetes)
34+
if err != nil {
35+
log.Fatalf("Failed to create OVN-K MCP server: %v", err)
36+
}
37+
log.Println("Adding Kubernetes tools to OVN-K MCP server")
38+
k8sMcpServer.AddTools(ovnkMcpServer)
39+
}
40+
41+
// Create a context that can be cancelled to shutdown the server.
42+
ctx, cancel := context.WithCancel(context.Background())
43+
44+
// Create a channel to receive signals to shutdown the server.
45+
signalChan := make(chan os.Signal, 1)
46+
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
47+
48+
// Start a goroutine to handle signals to shutdown the server.
49+
var server *http.Server
50+
go func() {
51+
// Wait for a signal to shutdown the server.
52+
<-signalChan
53+
54+
log.Printf("Shutting down server")
55+
56+
// Cancel the context to shutdown the server.
57+
defer cancel()
58+
59+
// Shutdown the http server if it is running.
60+
if server != nil {
61+
// Shutdown the http server.
62+
if err := server.Shutdown(ctx); err != nil {
63+
log.Printf("Failed to shutdown server: %v", err)
64+
}
65+
}
66+
}()
67+
68+
switch serverCfg.Transport {
69+
case "stdio":
70+
t := &mcp.LoggingTransport{Transport: &mcp.StdioTransport{}, Writer: os.Stderr}
71+
if err := ovnkMcpServer.Run(ctx, t); err != nil && err != context.Canceled {
72+
log.Printf("Server failed: %v", err)
73+
}
74+
case "http":
75+
handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server {
76+
return ovnkMcpServer
77+
}, nil)
78+
log.Printf("Listening on localhost:%s", serverCfg.Port)
79+
server = &http.Server{
80+
Addr: fmt.Sprintf("localhost:%s", serverCfg.Port),
81+
Handler: handler,
82+
}
83+
if err := server.ListenAndServe(); err != nil {
84+
log.Printf("HTTP server failed: %v", err)
85+
}
86+
default:
87+
log.Fatalf("Invalid transport: %s", serverCfg.Transport)
88+
}
89+
}
90+
91+
func parseFlags() *MCPServerConfig {
92+
cfg := &MCPServerConfig{}
93+
flag.StringVar(&cfg.Mode, "mode", "live-cluster", "Mode of debugging: live-cluster or offline")
94+
flag.StringVar(&cfg.Transport, "transport", "stdio", "Transport to use: stdio or http")
95+
flag.StringVar(&cfg.Port, "port", "8080", "Port to use")
96+
flag.StringVar(&cfg.Kubernetes.Kubeconfig, "kubeconfig", "", "Path to the kubeconfig file")
97+
flag.Parse()
98+
return cfg
99+
}

go.mod

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
module github.com/ovn-kubernetes/ovn-kubernetes-mcp
2+
3+
go 1.24.0
4+
5+
toolchain go1.24.7
6+
7+
require (
8+
github.com/google/go-cmp v0.7.0
9+
github.com/k8snetworkplumbingwg/ipamclaims v0.5.1-alpha
10+
github.com/k8snetworkplumbingwg/multi-networkpolicy v1.0.1
11+
github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7
12+
github.com/metallb/frr-k8s v0.0.20
13+
github.com/modelcontextprotocol/go-sdk v1.0.0
14+
github.com/onsi/ginkgo/v2 v2.26.0
15+
github.com/onsi/gomega v1.38.2
16+
github.com/openshift/client-go v0.0.0-20250922201106-dd37bfd2e597
17+
github.com/ovn-org/ovn-kubernetes/go-controller v0.0.0-20251009170312-7bc123a26688
18+
k8s.io/api v0.34.1
19+
k8s.io/apimachinery v0.34.1
20+
k8s.io/client-go v0.34.1
21+
k8s.io/kubectl v0.34.1
22+
k8s.io/kubernetes v1.34.1
23+
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
24+
sigs.k8s.io/controller-runtime v0.22.2
25+
sigs.k8s.io/network-policy-api v0.1.7
26+
sigs.k8s.io/yaml v1.6.0
27+
)
28+
29+
require (
30+
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
31+
github.com/MakeNowJust/heredoc v1.0.0 // indirect
32+
github.com/Masterminds/semver/v3 v3.4.0 // indirect
33+
github.com/beorn7/perks v1.0.1 // indirect
34+
github.com/blang/semver/v4 v4.0.0 // indirect
35+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
36+
github.com/chai2010/gettext-go v1.0.3 // indirect
37+
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
38+
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
39+
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
40+
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
41+
github.com/fatih/camelcase v1.0.0 // indirect
42+
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
43+
github.com/go-errors/errors v1.5.1 // indirect
44+
github.com/go-logr/logr v1.4.3 // indirect
45+
github.com/go-openapi/jsonpointer v0.22.1 // indirect
46+
github.com/go-openapi/jsonreference v0.21.2 // indirect
47+
github.com/go-openapi/swag v0.25.1 // indirect
48+
github.com/go-openapi/swag/cmdutils v0.25.1 // indirect
49+
github.com/go-openapi/swag/conv v0.25.1 // indirect
50+
github.com/go-openapi/swag/fileutils v0.25.1 // indirect
51+
github.com/go-openapi/swag/jsonname v0.25.1 // indirect
52+
github.com/go-openapi/swag/jsonutils v0.25.1 // indirect
53+
github.com/go-openapi/swag/loading v0.25.1 // indirect
54+
github.com/go-openapi/swag/mangling v0.25.1 // indirect
55+
github.com/go-openapi/swag/netutils v0.25.1 // indirect
56+
github.com/go-openapi/swag/stringutils v0.25.1 // indirect
57+
github.com/go-openapi/swag/typeutils v0.25.1 // indirect
58+
github.com/go-openapi/swag/yamlutils v0.25.1 // indirect
59+
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
60+
github.com/gogo/protobuf v1.3.2 // indirect
61+
github.com/google/btree v1.1.3 // indirect
62+
github.com/google/gnostic-models v0.7.0 // indirect
63+
github.com/google/jsonschema-go v0.3.0 // indirect
64+
github.com/google/pprof v0.0.0-20251007162407-5df77e3f7d1d // indirect
65+
github.com/google/uuid v1.6.0 // indirect
66+
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
67+
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
68+
github.com/inconshreveable/mousetrap v1.1.0 // indirect
69+
github.com/json-iterator/go v1.1.12 // indirect
70+
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
71+
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
72+
github.com/moby/spdystream v0.5.0 // indirect
73+
github.com/moby/term v0.5.2 // indirect
74+
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
75+
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
76+
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
77+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
78+
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
79+
github.com/openshift/api v0.0.0-20251009160459-595e66a09a84 // indirect
80+
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
81+
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
82+
github.com/prometheus/client_golang v1.23.2 // indirect
83+
github.com/prometheus/client_model v0.6.2 // indirect
84+
github.com/prometheus/common v0.67.1 // indirect
85+
github.com/prometheus/procfs v0.17.0 // indirect
86+
github.com/russross/blackfriday/v2 v2.1.0 // indirect
87+
github.com/spf13/cobra v1.10.1 // indirect
88+
github.com/spf13/pflag v1.0.10 // indirect
89+
github.com/x448/float16 v0.8.4 // indirect
90+
github.com/xlab/treeprint v1.2.0 // indirect
91+
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
92+
go.opentelemetry.io/otel v1.38.0 // indirect
93+
go.opentelemetry.io/otel/trace v1.38.0 // indirect
94+
go.uber.org/automaxprocs v1.6.0 // indirect
95+
go.yaml.in/yaml/v2 v2.4.3 // indirect
96+
go.yaml.in/yaml/v3 v3.0.4 // indirect
97+
golang.org/x/mod v0.29.0 // indirect
98+
golang.org/x/net v0.46.0 // indirect
99+
golang.org/x/oauth2 v0.32.0 // indirect
100+
golang.org/x/sync v0.17.0 // indirect
101+
golang.org/x/sys v0.37.0 // indirect
102+
golang.org/x/term v0.36.0 // indirect
103+
golang.org/x/text v0.30.0 // indirect
104+
golang.org/x/time v0.14.0 // indirect
105+
golang.org/x/tools v0.38.0 // indirect
106+
google.golang.org/protobuf v1.36.10 // indirect
107+
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
108+
gopkg.in/inf.v0 v0.9.1 // indirect
109+
k8s.io/apiserver v0.34.1 // indirect
110+
k8s.io/cli-runtime v0.34.1 // indirect
111+
k8s.io/component-base v0.34.1 // indirect
112+
k8s.io/component-helpers v0.34.1 // indirect
113+
k8s.io/klog/v2 v2.130.1 // indirect
114+
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect
115+
k8s.io/pod-security-admission v0.34.1 // indirect
116+
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
117+
sigs.k8s.io/kustomize/api v0.20.1 // indirect
118+
sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
119+
sigs.k8s.io/randfill v1.0.0 // indirect
120+
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
121+
)

0 commit comments

Comments
 (0)