Skip to content

Commit ca0aa46

Browse files
authored
feat(mcp): log tool call (function name + arguments)
Signed-off-by: Marc Nuri <[email protected]>
1 parent 3fbfd8d commit ca0aa46

File tree

5 files changed

+55
-3
lines changed

5 files changed

+55
-3
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
_output/
12
.idea/
23
.vscode/
34
.docusaurus/

pkg/http/authorization.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func AuthorizationMiddleware(requireOAuth bool, serverURL string, oidcProvider *
6969
http.Error(w, "Unauthorized: Invalid token", http.StatusUnauthorized)
7070
return
7171
}
72-
72+
7373
if oidcProvider != nil {
7474
// If OIDC Provider is configured, this token must be validated against it.
7575
if err := validateTokenWithOIDC(r.Context(), oidcProvider, token, audience); err != nil {

pkg/mcp/common_test.go

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

33
import (
4+
"bytes"
45
"context"
56
"encoding/json"
7+
"flag"
68
"fmt"
9+
"k8s.io/klog/v2"
10+
"k8s.io/klog/v2/textlogger"
711
"net/http/httptest"
812
"os"
913
"path/filepath"
1014
"runtime"
15+
"strconv"
1116
"testing"
1217
"time"
1318

@@ -97,6 +102,7 @@ func TestMain(m *testing.M) {
97102
type mcpContext struct {
98103
profile Profile
99104
listOutput output.Output
105+
logLevel int
100106

101107
staticConfig *config.StaticConfig
102108
clientOptions []transport.ClientOption
@@ -108,6 +114,8 @@ type mcpContext struct {
108114
mcpServer *Server
109115
mcpHttpServer *httptest.Server
110116
mcpClient *client.Client
117+
klogState klog.State
118+
logBuffer bytes.Buffer
111119
}
112120

113121
func (c *mcpContext) beforeEach(t *testing.T) {
@@ -130,6 +138,13 @@ func (c *mcpContext) beforeEach(t *testing.T) {
130138
if c.before != nil {
131139
c.before(c)
132140
}
141+
// Set up logging
142+
c.klogState = klog.CaptureState()
143+
flags := flag.NewFlagSet("test", flag.ContinueOnError)
144+
klog.InitFlags(flags)
145+
_ = flags.Set("v", strconv.Itoa(c.logLevel))
146+
klog.SetLogger(textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(c.logLevel), textlogger.Output(&c.logBuffer))))
147+
// MCP Server
133148
if c.mcpServer, err = NewServer(Configuration{
134149
Profile: c.profile,
135150
ListOutput: c.listOutput,
@@ -143,6 +158,7 @@ func (c *mcpContext) beforeEach(t *testing.T) {
143158
t.Fatal(err)
144159
return
145160
}
161+
// MCP Client
146162
if err = c.mcpClient.Start(c.ctx); err != nil {
147163
t.Fatal(err)
148164
return
@@ -165,6 +181,7 @@ func (c *mcpContext) afterEach() {
165181
c.mcpServer.Close()
166182
_ = c.mcpClient.Close()
167183
c.mcpHttpServer.Close()
184+
c.klogState.Restore()
168185
}
169186

170187
func testCase(t *testing.T, test func(c *mcpContext)) {

pkg/mcp/mcp.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package mcp
33
import (
44
"context"
55
"fmt"
6+
"k8s.io/klog/v2"
67
"net/http"
78
"slices"
89

@@ -56,6 +57,7 @@ func NewServer(configuration Configuration) (*Server, error) {
5657
server.WithPromptCapabilities(true),
5758
server.WithToolCapabilities(true),
5859
server.WithLogging(),
60+
server.WithToolHandlerMiddleware(toolCallLoggingMiddleware),
5961
),
6062
}
6163
if err := s.reloadKubernetesClient(); err != nil {
@@ -165,3 +167,10 @@ func contextFunc(ctx context.Context, r *http.Request) context.Context {
165167

166168
return ctx
167169
}
170+
171+
func toolCallLoggingMiddleware(next server.ToolHandlerFunc) server.ToolHandlerFunc {
172+
return func(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
173+
klog.V(5).Infof("mcp tool call: %s(%v)", ctr.Params.Name, ctr.Params.Arguments)
174+
return next(ctx, ctr)
175+
}
176+
}

pkg/mcp/mcp_tools_test.go

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package mcp
22

33
import (
4+
"github.com/mark3labs/mcp-go/mcp"
45
"k8s.io/utils/ptr"
6+
"regexp"
7+
"strings"
58
"testing"
69

7-
"github.com/mark3labs/mcp-go/mcp"
8-
910
"github.com/manusa/kubernetes-mcp-server/pkg/config"
1011
)
1112

@@ -116,3 +117,27 @@ func TestDisabledTools(t *testing.T) {
116117
})
117118
})
118119
}
120+
121+
func TestToolCallLogging(t *testing.T) {
122+
testCaseWithContext(t, &mcpContext{logLevel: 5}, func(c *mcpContext) {
123+
_, _ = c.callTool("configuration_view", map[string]interface{}{
124+
"minified": false,
125+
})
126+
t.Run("Logs tool name", func(t *testing.T) {
127+
expectedLog := "mcp tool call: configuration_view("
128+
if !strings.Contains(c.logBuffer.String(), expectedLog) {
129+
t.Errorf("Expected log to contain '%s', got: %s", expectedLog, c.logBuffer.String())
130+
}
131+
})
132+
t.Run("Logs tool call arguments", func(t *testing.T) {
133+
expected := `"mcp tool call: configuration_view\((.+)\)"`
134+
m := regexp.MustCompile(expected).FindStringSubmatch(c.logBuffer.String())
135+
if len(m) != 2 {
136+
t.Fatalf("Expected log entry to contain arguments, got %s", c.logBuffer.String())
137+
}
138+
if m[1] != "map[minified:false]" {
139+
t.Errorf("Expected log arguments to be 'map[minified:false]', got %s", m[1])
140+
}
141+
})
142+
})
143+
}

0 commit comments

Comments
 (0)