Skip to content

Commit 70882a3

Browse files
authored
feat: add support for mcp server instructions (#579)
* feat: add support for mcp server instructions Signed-off-by: Calum Murray <[email protected]> * test: verify mcp server instructions set properly Signed-off-by: Calum Murray <[email protected]> --------- Signed-off-by: Calum Murray <[email protected]>
1 parent dae71e8 commit 70882a3

File tree

3 files changed

+77
-0
lines changed

3 files changed

+77
-0
lines changed

pkg/config/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ type StaticConfig struct {
8181
promptsDefined bool // Internal: tracks if prompts were defined in config
8282
promptsMetadata toml.MetaData // Internal: metadata for prompts decoding
8383

84+
// Server instructions to be provided by the MCP server to the MCP client
85+
// This can be used to provide specific instructions on how the client should use the server
86+
ServerInstructions string `toml:"server_instructions,omitempty"`
87+
8488
// Internal: parsed provider configs (not exposed to TOML package)
8589
parsedClusterProviderConfigs map[string]configapi.Extended
8690
// Internal: parsed toolset configs (not exposed to TOML package)

pkg/mcp/mcp.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ func NewServer(configuration Configuration) (*Server, error) {
8282
HasResources: false,
8383
HasPrompts: true,
8484
HasTools: true,
85+
Instructions: configuration.ServerInstructions,
8586
}),
8687
}
8788

pkg/mcp/mcp_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package mcp
22

33
import (
4+
"context"
45
"net/http"
6+
"net/http/httptest"
57
"sync"
68
"testing"
79

810
"github.com/containers/kubernetes-mcp-server/internal/test"
11+
"github.com/mark3labs/mcp-go/client"
912
"github.com/mark3labs/mcp-go/client/transport"
1013
"github.com/stretchr/testify/suite"
1114
)
@@ -97,3 +100,72 @@ func (s *McpHeadersSuite) TestAuthorizationHeaderPropagation() {
97100
func TestMcpHeaders(t *testing.T) {
98101
suite.Run(t, new(McpHeadersSuite))
99102
}
103+
104+
type ServerInstructionsSuite struct {
105+
BaseMcpSuite
106+
mockServer *test.MockServer
107+
}
108+
109+
func (s *ServerInstructionsSuite) SetupTest() {
110+
s.BaseMcpSuite.SetupTest()
111+
s.mockServer = test.NewMockServer()
112+
s.mockServer.Handle(&test.DiscoveryClientHandler{})
113+
s.Cfg.KubeConfig = s.mockServer.KubeconfigFile(s.T())
114+
}
115+
116+
func (s *ServerInstructionsSuite) TearDownTest() {
117+
s.BaseMcpSuite.TearDownTest()
118+
if s.mockServer != nil {
119+
s.mockServer.Close()
120+
}
121+
}
122+
123+
func (s *ServerInstructionsSuite) TestServerInstructions() {
124+
s.Run("returns empty instructions when not configured", func() {
125+
s.Cfg.ServerInstructions = ""
126+
mcpServer, err := NewServer(Configuration{StaticConfig: s.Cfg})
127+
s.Require().NoError(err)
128+
s.T().Cleanup(mcpServer.Close)
129+
130+
testServer := httptest.NewServer(mcpServer.ServeHTTP())
131+
s.T().Cleanup(testServer.Close)
132+
133+
mcpClient, err := client.NewStreamableHttpClient(testServer.URL+"/mcp", transport.WithContinuousListening())
134+
s.Require().NoError(err)
135+
s.T().Cleanup(func() { _ = mcpClient.Close() })
136+
137+
err = mcpClient.Start(context.Background())
138+
s.Require().NoError(err)
139+
140+
result, err := mcpClient.Initialize(context.Background(), test.McpInitRequest())
141+
s.Require().NoError(err)
142+
s.Empty(result.Instructions, "instructions should be empty when not configured")
143+
})
144+
145+
s.Run("returns configured instructions", func() {
146+
expectedInstructions := "Always use YAML output format for kubectl commands."
147+
s.Cfg.ServerInstructions = expectedInstructions
148+
149+
mcpServer, err := NewServer(Configuration{StaticConfig: s.Cfg})
150+
s.Require().NoError(err)
151+
s.T().Cleanup(mcpServer.Close)
152+
153+
testServer := httptest.NewServer(mcpServer.ServeHTTP())
154+
s.T().Cleanup(testServer.Close)
155+
156+
mcpClient, err := client.NewStreamableHttpClient(testServer.URL+"/mcp", transport.WithContinuousListening())
157+
s.Require().NoError(err)
158+
s.T().Cleanup(func() { _ = mcpClient.Close() })
159+
160+
err = mcpClient.Start(context.Background())
161+
s.Require().NoError(err)
162+
163+
result, err := mcpClient.Initialize(context.Background(), test.McpInitRequest())
164+
s.Require().NoError(err)
165+
s.Equal(expectedInstructions, result.Instructions, "instructions should match configured value")
166+
})
167+
}
168+
169+
func TestServerInstructions(t *testing.T) {
170+
suite.Run(t, new(ServerInstructionsSuite))
171+
}

0 commit comments

Comments
 (0)