dd239d8443
* initial logging stack for http * add metrics adapter * fix linter issues * make log fields generic * Update pkg/github/server_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Remove unused SlogMetrics adapter The slog-based metrics adapter was never used — OSS always uses NoopMetrics and the remote server has its own DataDog-backed adapter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Update pkg/github/dependencies.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fmt * change to use slog * address feedback * rename noop adapter to noop sink * Update pkg/http/server.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * [WIP] [WIP] Address feedback on OSS logging adapter for http implementation (#2264) * Initial plan * Fix BaseDeps.Logger and BaseDeps.Metrics to return safe defaults when Obsv is nil Agent-Logs-Url: https://github.com/github/github-mcp-server/sessions/53221b0b-abb4-4138-a147-3ce9e13b379a Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com> * Fix nil metrics in server.go by passing metrics.NewNoopMetrics() to NewExporters Agent-Logs-Url: https://github.com/github/github-mcp-server/sessions/53221b0b-abb4-4138-a147-3ce9e13b379a Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com> Co-authored-by: Matt Holloway <mattdholloway@github.com> * replace nil with stubs --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
237 lines
7.0 KiB
Go
237 lines
7.0 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/github/github-mcp-server/pkg/inventory"
|
|
"github.com/github/github-mcp-server/pkg/translations"
|
|
"github.com/google/jsonschema-go/jsonschema"
|
|
"github.com/modelcontextprotocol/go-sdk/mcp"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// createDynamicRequest creates an MCP request with the given arguments for dynamic tools.
|
|
func createDynamicRequest(args map[string]any) *mcp.CallToolRequest {
|
|
argsJSON, _ := json.Marshal(args)
|
|
return &mcp.CallToolRequest{
|
|
Params: &mcp.CallToolParamsRaw{
|
|
Arguments: json.RawMessage(argsJSON),
|
|
},
|
|
}
|
|
}
|
|
|
|
func TestDynamicTools_ListAvailableToolsets(t *testing.T) {
|
|
// Build a registry with no toolsets enabled (dynamic mode)
|
|
reg, err := NewInventory(translations.NullTranslationHelper).
|
|
WithToolsets([]string{}).
|
|
Build()
|
|
require.NoError(t, err)
|
|
|
|
// Create a mock server
|
|
server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil)
|
|
|
|
// Create dynamic tool dependencies
|
|
deps := DynamicToolDependencies{
|
|
Server: server,
|
|
Inventory: reg,
|
|
ToolDeps: nil,
|
|
T: translations.NullTranslationHelper,
|
|
}
|
|
|
|
// Get the list_available_toolsets tool
|
|
tool := ListAvailableToolsets()
|
|
handler := tool.Handler(deps)
|
|
|
|
// Call the handler
|
|
result, err := handler(context.Background(), createDynamicRequest(map[string]any{}))
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
require.Len(t, result.Content, 1)
|
|
|
|
// Parse the result
|
|
var toolsets []map[string]string
|
|
textContent := result.Content[0].(*mcp.TextContent)
|
|
err = json.Unmarshal([]byte(textContent.Text), &toolsets)
|
|
require.NoError(t, err)
|
|
|
|
// Verify we got toolsets
|
|
assert.NotEmpty(t, toolsets, "should have available toolsets")
|
|
|
|
// Find the repos toolset and verify it's not enabled
|
|
var reposToolset map[string]string
|
|
for _, ts := range toolsets {
|
|
if ts["name"] == "repos" {
|
|
reposToolset = ts
|
|
break
|
|
}
|
|
}
|
|
require.NotNil(t, reposToolset, "repos toolset should exist")
|
|
assert.Equal(t, "false", reposToolset["currently_enabled"], "repos should not be enabled initially")
|
|
}
|
|
|
|
func TestDynamicTools_GetToolsetTools(t *testing.T) {
|
|
// Build a registry with no toolsets enabled (dynamic mode)
|
|
reg, err := NewInventory(translations.NullTranslationHelper).
|
|
WithToolsets([]string{}).
|
|
Build()
|
|
require.NoError(t, err)
|
|
|
|
// Create a mock server
|
|
server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil)
|
|
|
|
// Create dynamic tool dependencies
|
|
deps := DynamicToolDependencies{
|
|
Server: server,
|
|
Inventory: reg,
|
|
ToolDeps: nil,
|
|
T: translations.NullTranslationHelper,
|
|
}
|
|
|
|
// Get the get_toolset_tools tool
|
|
tool := GetToolsetsTools(reg)
|
|
handler := tool.Handler(deps)
|
|
|
|
// Call the handler for repos toolset
|
|
result, err := handler(context.Background(), createDynamicRequest(map[string]any{
|
|
"toolset": "repos",
|
|
}))
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
require.Len(t, result.Content, 1)
|
|
|
|
// Parse the result
|
|
var tools []map[string]string
|
|
textContent := result.Content[0].(*mcp.TextContent)
|
|
err = json.Unmarshal([]byte(textContent.Text), &tools)
|
|
require.NoError(t, err)
|
|
|
|
// Verify we got tools for the repos toolset
|
|
assert.NotEmpty(t, tools, "repos toolset should have tools")
|
|
|
|
// Verify at least get_commit is there (a repos toolset tool)
|
|
var foundGetCommit bool
|
|
for _, tool := range tools {
|
|
if tool["name"] == "get_commit" {
|
|
foundGetCommit = true
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, foundGetCommit, "get_commit should be in repos toolset")
|
|
}
|
|
|
|
func TestDynamicTools_EnableToolset(t *testing.T) {
|
|
// Build a registry with no toolsets enabled (dynamic mode)
|
|
reg, err := NewInventory(translations.NullTranslationHelper).
|
|
WithToolsets([]string{}).
|
|
Build()
|
|
require.NoError(t, err)
|
|
|
|
// Create a mock server
|
|
server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil)
|
|
|
|
// Create dynamic tool dependencies
|
|
deps := DynamicToolDependencies{
|
|
Server: server,
|
|
Inventory: reg,
|
|
ToolDeps: NewBaseDeps(nil, nil, nil, nil, translations.NullTranslationHelper, FeatureFlags{}, 0, nil, stubExporters()),
|
|
T: translations.NullTranslationHelper,
|
|
}
|
|
|
|
// Verify repos is not enabled initially
|
|
assert.False(t, reg.IsToolsetEnabled(inventory.ToolsetID("repos")))
|
|
|
|
// Get the enable_toolset tool
|
|
tool := EnableToolset(reg)
|
|
handler := tool.Handler(deps)
|
|
|
|
// Enable the repos toolset
|
|
result, err := handler(context.Background(), createDynamicRequest(map[string]any{
|
|
"toolset": "repos",
|
|
}))
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
require.Len(t, result.Content, 1)
|
|
|
|
// Verify the toolset is now enabled
|
|
assert.True(t, reg.IsToolsetEnabled(inventory.ToolsetID("repos")), "repos should be enabled after enable_toolset")
|
|
|
|
// Verify the success message
|
|
textContent := result.Content[0].(*mcp.TextContent)
|
|
assert.Contains(t, textContent.Text, "enabled")
|
|
|
|
// Try enabling again - should say already enabled
|
|
result2, err := handler(context.Background(), createDynamicRequest(map[string]any{
|
|
"toolset": "repos",
|
|
}))
|
|
require.NoError(t, err)
|
|
textContent2 := result2.Content[0].(*mcp.TextContent)
|
|
assert.Contains(t, textContent2.Text, "already enabled")
|
|
}
|
|
|
|
func TestDynamicTools_EnableToolset_InvalidToolset(t *testing.T) {
|
|
// Build a registry with no toolsets enabled (dynamic mode)
|
|
reg, err := NewInventory(translations.NullTranslationHelper).
|
|
WithToolsets([]string{}).
|
|
Build()
|
|
require.NoError(t, err)
|
|
|
|
// Create a mock server
|
|
server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil)
|
|
|
|
// Create dynamic tool dependencies
|
|
deps := DynamicToolDependencies{
|
|
Server: server,
|
|
Inventory: reg,
|
|
ToolDeps: nil,
|
|
T: translations.NullTranslationHelper,
|
|
}
|
|
|
|
// Get the enable_toolset tool
|
|
tool := EnableToolset(reg)
|
|
handler := tool.Handler(deps)
|
|
|
|
// Try to enable a non-existent toolset
|
|
result, err := handler(context.Background(), createDynamicRequest(map[string]any{
|
|
"toolset": "nonexistent",
|
|
}))
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
|
|
// Should be an error result
|
|
textContent := result.Content[0].(*mcp.TextContent)
|
|
assert.Contains(t, textContent.Text, "not found")
|
|
}
|
|
|
|
func TestDynamicTools_ToolsetsEnum(t *testing.T) {
|
|
// Build a registry
|
|
reg, err := NewInventory(translations.NullTranslationHelper).Build()
|
|
require.NoError(t, err)
|
|
|
|
// Get tools to verify they have proper enum values
|
|
tools := DynamicTools(reg)
|
|
|
|
// Find enable_toolset and get_toolset_tools
|
|
for _, tool := range tools {
|
|
if tool.Tool.Name == "enable_toolset" || tool.Tool.Name == "get_toolset_tools" {
|
|
// Verify the toolset property has an enum
|
|
schema := tool.Tool.InputSchema.(*jsonschema.Schema)
|
|
toolsetProp := schema.Properties["toolset"]
|
|
require.NotNil(t, toolsetProp, "toolset property should exist")
|
|
assert.NotEmpty(t, toolsetProp.Enum, "toolset property should have enum values")
|
|
|
|
// Verify repos is in the enum
|
|
var foundRepos bool
|
|
for _, v := range toolsetProp.Enum {
|
|
if v == inventory.ToolsetID("repos") {
|
|
foundRepos = true
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, foundRepos, "repos should be in toolset enum for %s", tool.Tool.Name)
|
|
}
|
|
}
|
|
}
|