Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ require (
github.com/asticode/go-astisub v0.34.0
github.com/gin-gonic/gin v1.10.0
github.com/google/go-github/v41 v41.0.0
github.com/google/jsonschema-go v0.2.3
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This got moved out into it's own package somewhere between v0.2.0 and v0.5.0 of github.com/modelcontextprotocol/go-sdk

github.com/jmoiron/sqlx v1.4.0
github.com/lib/pq v1.10.9
github.com/mark3labs/mcp-go v0.34.0
github.com/mattermost/mattermost/server/public v0.1.12
github.com/mattermost/testcontainers-mattermost-go v0.0.0-20250129100554-3cf1ce84b0e4
github.com/modelcontextprotocol/go-sdk v0.2.0
github.com/modelcontextprotocol/go-sdk v0.6.0
github.com/nicksnyder/go-i18n/v2 v2.5.1
github.com/openai/openai-go/v2 v2.0.2
github.com/pgvector/pgvector-go v0.3.0
Expand Down Expand Up @@ -127,7 +127,6 @@ require (
github.com/russellhaering/goxmldsig v1.5.0 // indirect
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
github.com/shoenig/go-m1cpu v0.1.6 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/testcontainers/testcontainers-go v0.35.0 // indirect
Expand Down
16 changes: 6 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/francoispqt/gojay v1.2.13 h1:d2m3sFjloqoIUQU3TsHBgj6qg/BVGlTBeHDUmyJnXKk=
github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
Expand Down Expand Up @@ -155,8 +153,8 @@ github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
Expand All @@ -182,6 +180,8 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/jsonschema-go v0.2.3 h1:dkP3B96OtZKKFvdrUSaDkL+YDx8Uw9uC4Y+eukpCnmM=
github.com/google/jsonschema-go v0.2.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
Expand Down Expand Up @@ -268,8 +268,6 @@ github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mark3labs/mcp-go v0.34.0 h1:eWy7WBGvhk6EyAAyVzivTCprE52iXJwNtvHV6Cv3bR0=
github.com/mark3labs/mcp-go v0.34.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 h1:Khvh6waxG1cHc4Cz5ef9n3XVCxRWpAKUtqg9PJl5+y8=
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34=
github.com/mattermost/gosaml2 v0.8.0 h1:nkYiByawqwJ7KncK1LDWKwTx5aRarBTQsmH+XcCVsWQ=
Expand Down Expand Up @@ -309,8 +307,8 @@ github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modelcontextprotocol/go-sdk v0.2.0 h1:PESNYOmyM1c369tRkzXLY5hHrazj8x9CY1Xu0fLCryM=
github.com/modelcontextprotocol/go-sdk v0.2.0/go.mod h1:0sL9zUKKs2FTTkeCCVnKqbLJTw5TScefPAzojjU459E=
github.com/modelcontextprotocol/go-sdk v0.6.0 h1:cmtMYfRAUtEtCiuorOWPj7ygcypfuB2FgFEDBqZqgy4=
github.com/modelcontextprotocol/go-sdk v0.6.0/go.mod h1:djQKZ74bEV+UMAmyG/L0coVhV0HM3fpVtGuUPls0znc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down Expand Up @@ -415,8 +413,6 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
Expand Down
2 changes: 1 addition & 1 deletion llm/language_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
package llm

import (
"github.com/modelcontextprotocol/go-sdk/jsonschema"
"github.com/google/jsonschema-go/jsonschema"
)

type LanguageModel interface {
Expand Down
4 changes: 2 additions & 2 deletions llm/tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"errors"
"fmt"

"github.com/modelcontextprotocol/go-sdk/jsonschema"
"github.com/google/jsonschema-go/jsonschema"
)

// Tool represents a function that can be called by the language model during a conversation.
Expand Down Expand Up @@ -77,7 +77,7 @@ type TraceLog interface {
// NewJSONSchemaFromStruct creates a JSONSchema from a Go struct using generics
// It's a helper function for tool providers that currently define schemas as structs
func NewJSONSchemaFromStruct[T any]() *jsonschema.Schema {
schema, err := jsonschema.For[T]()
schema, err := jsonschema.For[T](nil)
if err != nil {
panic(fmt.Sprintf("failed to create JSON schema from struct: %v", err))
}
Expand Down
12 changes: 7 additions & 5 deletions mcp/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,13 @@ func (c *Client) createSession(ctx context.Context, serverConfig ServerConfig) (
httpClient := c.httpClient(headers)

// Create an SSE transport with the authenticated HTTP client
transport := mcp.NewSSEClientTransport(serverConfig.BaseURL, &mcp.SSEClientTransportOptions{
transport := &mcp.SSEClientTransport{
Endpoint: serverConfig.BaseURL,
HTTPClient: httpClient,
})
}

// Try to connect using the OAuth-enabled SSE transport
session, errSSEConnect := client.Connect(ctx, transport)
session, errSSEConnect := client.Connect(ctx, transport, nil)
if errSSEConnect == nil {
// Successfully connected with OAuth
return session, nil
Expand All @@ -117,9 +118,10 @@ func (c *Client) createSession(ctx context.Context, serverConfig ServerConfig) (
}

// Unauthenticated HTTP
session, errUnauthHTTP := client.Connect(ctx, mcp.NewStreamableClientTransport(serverConfig.BaseURL, &mcp.StreamableClientTransportOptions{
session, errUnauthHTTP := client.Connect(ctx, &mcp.StreamableClientTransport{
Endpoint: serverConfig.BaseURL,
HTTPClient: httpClient,
}))
}, nil)
if errUnauthHTTP == nil {
// Successfully connected without authentication
return session, nil
Expand Down
69 changes: 34 additions & 35 deletions mcpserver/dev_tools_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package mcpserver_test
import (
"testing"

"github.com/mark3labs/mcp-go/mcp"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -55,8 +55,8 @@ func TestDevToolsWithDevModeEnabled(t *testing.T) {
"last_name": "User",
}

result := executeDevToolWithMCP(t, suite, "create_user", args)
assert.False(t, result.IsError, "create_user should succeed in dev mode")
result, err := executeDevToolWithMCP(t, suite, "create_user", args)
require.NoError(t, err, "create_user should succeed in dev mode")
assert.NotEmpty(t, result.Content, "create_user should return content")
})

Expand All @@ -66,8 +66,8 @@ func TestDevToolsWithDevModeEnabled(t *testing.T) {
// missing email and password
}

result := executeDevToolWithMCP(t, suite, "create_user", args)
assert.True(t, result.IsError, "create_user should fail with missing required fields")
_, err := executeDevToolWithMCP(t, suite, "create_user", args)
require.Error(t, err, "create_user should fail with missing required fields")
})
})

Expand All @@ -80,8 +80,8 @@ func TestDevToolsWithDevModeEnabled(t *testing.T) {
"description": "Team created for dev testing",
}

result := executeDevToolWithMCP(t, suite, "create_team", args)
assert.False(t, result.IsError, "create_team should succeed in dev mode")
result, err := executeDevToolWithMCP(t, suite, "create_team", args)
require.NoError(t, err, "create_team should succeed in dev mode")
assert.NotEmpty(t, result.Content, "create_team should return content")
})

Expand All @@ -92,8 +92,8 @@ func TestDevToolsWithDevModeEnabled(t *testing.T) {
"type": "X", // Invalid type
}

result := executeDevToolWithMCP(t, suite, "create_team", args)
assert.True(t, result.IsError, "create_team should fail with invalid type")
_, err := executeDevToolWithMCP(t, suite, "create_team", args)
require.Error(t, err, "create_team should fail with invalid type")
})
})

Expand All @@ -104,8 +104,8 @@ func TestDevToolsWithDevModeEnabled(t *testing.T) {
"email": "[email protected]",
"password": "password123",
}
userResult := executeDevToolWithMCP(t, suite, "create_user", userArgs)
require.False(t, userResult.IsError, "User creation should succeed for team test")
_, err := executeDevToolWithMCP(t, suite, "create_user", userArgs)
require.NoError(t, err, "User creation should succeed for team test")

t.Run("HappyPath", func(t *testing.T) {
// Extract user ID from result (simplified - in real implementation would parse JSON)
Expand All @@ -115,8 +115,9 @@ func TestDevToolsWithDevModeEnabled(t *testing.T) {
"team_id": testData.Team.Id,
}

result := executeDevToolWithMCP(t, suite, "add_user_to_team", args)
result, _ := executeDevToolWithMCP(t, suite, "add_user_to_team", args)
// User might already be in team, so we just check the call doesn't crash
// Don't require no error since user might already be in team
assert.NotNil(t, result, "add_user_to_team should return a result")
})

Expand All @@ -126,8 +127,8 @@ func TestDevToolsWithDevModeEnabled(t *testing.T) {
"team_id": testData.Team.Id,
}

result := executeDevToolWithMCP(t, suite, "add_user_to_team", args)
assert.True(t, result.IsError, "add_user_to_team should fail with invalid user ID")
_, err := executeDevToolWithMCP(t, suite, "add_user_to_team", args)
require.Error(t, err, "add_user_to_team should fail with invalid user ID")
})
})

Expand All @@ -138,8 +139,9 @@ func TestDevToolsWithDevModeEnabled(t *testing.T) {
"channel_id": testData.Channel.Id,
}

result := executeDevToolWithMCP(t, suite, "add_user_to_channel", args)
result, _ := executeDevToolWithMCP(t, suite, "add_user_to_channel", args)
// User might already be in channel, so we just check the call doesn't crash
// Don't require no error since user might already be in channel
assert.NotNil(t, result, "add_user_to_channel should return a result")
})

Expand All @@ -149,8 +151,8 @@ func TestDevToolsWithDevModeEnabled(t *testing.T) {
"channel_id": "invalid-channel-id",
}

result := executeDevToolWithMCP(t, suite, "add_user_to_channel", args)
assert.True(t, result.IsError, "add_user_to_channel should fail with invalid channel ID")
_, err := executeDevToolWithMCP(t, suite, "add_user_to_channel", args)
require.Error(t, err, "add_user_to_channel should fail with invalid channel ID")
})
})

Expand All @@ -161,21 +163,21 @@ func TestDevToolsWithDevModeEnabled(t *testing.T) {
"email": "[email protected]",
"password": "postpassword123",
}
userResult := executeDevToolWithMCP(t, suite, "create_user", userArgs)
require.False(t, userResult.IsError, "User creation should succeed for post test")
_, err := executeDevToolWithMCP(t, suite, "create_user", userArgs)
require.NoError(t, err, "User creation should succeed for post test")

// Add user to team and channel
addTeamArgs := map[string]interface{}{
"user_id": testData.User.Id, // Using existing user for simplicity
"team_id": testData.Team.Id,
}
executeDevToolWithMCP(t, suite, "add_user_to_team", addTeamArgs)
_, _ = executeDevToolWithMCP(t, suite, "add_user_to_team", addTeamArgs)

addChannelArgs := map[string]interface{}{
"user_id": testData.User.Id,
"channel_id": testData.Channel.Id,
}
executeDevToolWithMCP(t, suite, "add_user_to_channel", addChannelArgs)
_, _ = executeDevToolWithMCP(t, suite, "add_user_to_channel", addChannelArgs)

t.Run("HappyPath", func(t *testing.T) {
args := map[string]interface{}{
Expand All @@ -185,8 +187,9 @@ func TestDevToolsWithDevModeEnabled(t *testing.T) {
"message": "Hello from dev tool user!",
}

result := executeDevToolWithMCP(t, suite, "create_post_as_user", args)
result, _ := executeDevToolWithMCP(t, suite, "create_post_as_user", args)
// This might fail due to user permissions, but should not crash
// Don't require no error since it might fail due to permissions
assert.NotNil(t, result, "create_post_as_user should return a result")
})

Expand All @@ -198,8 +201,8 @@ func TestDevToolsWithDevModeEnabled(t *testing.T) {
"message": "This should fail",
}

result := executeDevToolWithMCP(t, suite, "create_post_as_user", args)
assert.True(t, result.IsError, "create_post_as_user should fail with wrong password")
_, err := executeDevToolWithMCP(t, suite, "create_post_as_user", args)
require.Error(t, err, "create_post_as_user should fail with wrong password")
})
})
}
Expand All @@ -226,22 +229,18 @@ func TestDevToolsSecurityGating(t *testing.T) {
"test": "value", // Generic args since they should be blocked anyway
}

result := executeDevToolWithMCP(t, suite, toolName, args)
assert.True(t, result.IsError, "Dev tool %s should be blocked when dev mode is disabled", toolName)
_, err := executeDevToolWithMCP(t, suite, toolName, args)
require.Error(t, err, "Dev tool %s should be blocked when dev mode is disabled", toolName)

// Check that the error indicates the tool is not available (correct security behavior)
if len(result.Content) > 0 {
if textContent, ok := result.Content[0].(mcp.TextContent); ok {
assert.Contains(t, textContent.Text, "not found",
"Error should indicate tool is not available when dev mode is disabled")
}
}
assert.Contains(t, err.Error(), "unknown tool",
"Error should indicate tool is not available when dev mode is disabled")
})
}
}

// executeDevToolWithMCP calls the dev MCP tool through the unified helper
func executeDevToolWithMCP(t *testing.T, suite *TestSuite, toolName string, args map[string]interface{}) *mcp.CallToolResult {
require.NotNil(t, suite.mcpServer, "MCP server must be created before calling tools")
// executeDevToolWithMCP creates a test MCP client session connected to the server and calls the dev tool
func executeDevToolWithMCP(t *testing.T, suite *TestSuite, toolName string, args map[string]interface{}) (*mcp.CallToolResult, error) {
require.NotNil(t, suite.mcpServer, "MCP server must be created before creating client sessions")
return testhelpers.ExecuteMCPTool(t, suite.mcpServer.GetMCPServer(), toolName, args)
}
Loading
Loading