chore(tests): add new tests for tool registration and resource templates

This commit is contained in:
Hein
2026-03-31 17:24:54 +02:00
parent f41c512f36
commit bb759f4683
4 changed files with 308 additions and 2 deletions

View File

@@ -3,6 +3,7 @@ package mcpserver
import (
"context"
"encoding/json"
"errors"
"strings"
"testing"
@@ -99,3 +100,130 @@ func TestAddToolReturnsSchemaErrorInsteadOfPanicking(t *testing.T) {
t.Fatalf("addTool() error = %q, want tool context", err.Error())
}
}
func TestAddToolAppliesInputDefaultsAndSetsStructuredContent(t *testing.T) {
server := mcp.NewServer(&mcp.Implementation{Name: "test", Version: "0.0.1"}, nil)
type helloInput struct {
Name string `json:"name,omitempty"`
}
type helloOutput struct {
Message string `json:"message"`
}
tool := &mcp.Tool{
Name: "hello",
InputSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
"name": {
Type: "string",
Default: json.RawMessage(`"world"`),
},
},
},
}
var gotInput helloInput
if err := addTool(server, nil, tool, func(_ context.Context, _ *mcp.CallToolRequest, in helloInput) (*mcp.CallToolResult, helloOutput, error) {
gotInput = in
return nil, helloOutput{Message: "hello " + in.Name}, nil
}); err != nil {
t.Fatalf("addTool() error = %v", err)
}
ct, st := mcp.NewInMemoryTransports()
_, err := server.Connect(context.Background(), st, nil)
if err != nil {
t.Fatalf("connect server: %v", err)
}
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "0.0.1"}, nil)
cs, err := client.Connect(context.Background(), ct, nil)
if err != nil {
t.Fatalf("connect client: %v", err)
}
defer func() {
_ = cs.Close()
}()
result, err := cs.CallTool(context.Background(), &mcp.CallToolParams{
Name: "hello",
Arguments: map[string]any{},
})
if err != nil {
t.Fatalf("CallTool(hello) error = %v", err)
}
if gotInput.Name != "world" {
t.Fatalf("handler input name = %q, want %q", gotInput.Name, "world")
}
gotStructured, ok := result.StructuredContent.(map[string]any)
if !ok {
t.Fatalf("structured content type = %T, want map[string]any", result.StructuredContent)
}
if gotStructured["message"] != "hello world" {
t.Fatalf("structured content message = %#v, want %q", gotStructured["message"], "hello world")
}
if len(result.Content) != 1 {
t.Fatalf("content count = %d, want 1", len(result.Content))
}
textContent, ok := result.Content[0].(*mcp.TextContent)
if !ok {
t.Fatalf("content[0] type = %T, want *mcp.TextContent", result.Content[0])
}
if textContent.Text != `{"message":"hello world"}` {
t.Fatalf("content[0].Text = %q, want %q", textContent.Text, `{"message":"hello world"}`)
}
}
func TestAddToolWrapsRegularErrorsInToolResults(t *testing.T) {
server := mcp.NewServer(&mcp.Implementation{Name: "test", Version: "0.0.1"}, nil)
toolErr := errors.New("boom")
if err := addTool(server, nil, &mcp.Tool{Name: "explode"}, func(_ context.Context, _ *mcp.CallToolRequest, _ struct{}) (*mcp.CallToolResult, struct{}, error) {
return nil, struct{}{}, toolErr
}); err != nil {
t.Fatalf("addTool() error = %v", err)
}
ct, st := mcp.NewInMemoryTransports()
_, err := server.Connect(context.Background(), st, nil)
if err != nil {
t.Fatalf("connect server: %v", err)
}
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "0.0.1"}, nil)
cs, err := client.Connect(context.Background(), ct, nil)
if err != nil {
t.Fatalf("connect client: %v", err)
}
defer func() {
_ = cs.Close()
}()
result, err := cs.CallTool(context.Background(), &mcp.CallToolParams{Name: "explode"})
if err != nil {
t.Fatalf("CallTool(explode) error = %v, want nil transport error", err)
}
if !result.IsError {
t.Fatal("CallTool(explode) IsError = false, want true")
}
if result.StructuredContent != nil {
t.Fatalf("structured content = %#v, want nil", result.StructuredContent)
}
if len(result.Content) != 1 {
t.Fatalf("content count = %d, want 1", len(result.Content))
}
textContent, ok := result.Content[0].(*mcp.TextContent)
if !ok {
t.Fatalf("content[0] type = %T, want *mcp.TextContent", result.Content[0])
}
if textContent.Text != toolErr.Error() {
t.Fatalf("content[0].Text = %q, want %q", textContent.Text, toolErr.Error())
}
}

View File

@@ -0,0 +1,151 @@
package mcpserver
import (
"context"
"net/http/httptest"
"reflect"
"sort"
"testing"
"time"
"github.com/modelcontextprotocol/go-sdk/mcp"
"git.warky.dev/wdevs/amcs/internal/config"
)
func TestNewListsAllRegisteredTools(t *testing.T) {
cs := newStreamableTestClient(t)
result, err := cs.ListTools(context.Background(), nil)
if err != nil {
t.Fatalf("ListTools() error = %v", err)
}
got := make([]string, 0, len(result.Tools))
for _, tool := range result.Tools {
got = append(got, tool.Name)
}
sort.Strings(got)
want := []string{
"add_activity",
"add_family_member",
"add_guardrail",
"add_household_item",
"add_important_date",
"add_maintenance_task",
"add_professional_contact",
"add_project_guardrail",
"add_project_skill",
"add_recipe",
"add_skill",
"add_vendor",
"archive_thought",
"backfill_embeddings",
"capture_thought",
"create_meal_plan",
"create_opportunity",
"create_project",
"delete_thought",
"generate_shopping_list",
"get_active_project",
"get_contact_history",
"get_follow_ups_due",
"get_household_item",
"get_meal_plan",
"get_project_context",
"get_thought",
"get_upcoming_dates",
"get_upcoming_maintenance",
"get_version_info",
"get_week_schedule",
"link_thought_to_contact",
"link_thoughts",
"list_family_members",
"list_files",
"list_guardrails",
"list_project_guardrails",
"list_project_skills",
"list_projects",
"list_skills",
"list_thoughts",
"list_vendors",
"load_file",
"log_interaction",
"log_maintenance",
"recall_context",
"related_thoughts",
"remove_guardrail",
"remove_project_guardrail",
"remove_project_skill",
"remove_skill",
"reparse_thought_metadata",
"retry_failed_metadata",
"save_file",
"search_activities",
"search_contacts",
"search_household_items",
"search_maintenance_history",
"search_recipes",
"search_thoughts",
"set_active_project",
"summarize_thoughts",
"thought_stats",
"update_recipe",
"update_thought",
"upload_file",
}
sort.Strings(want)
if !reflect.DeepEqual(got, want) {
t.Fatalf("ListTools() names = %#v, want %#v", got, want)
}
}
func TestNewListsStoredFileResourceTemplate(t *testing.T) {
cs := newStreamableTestClient(t)
result, err := cs.ListResourceTemplates(context.Background(), nil)
if err != nil {
t.Fatalf("ListResourceTemplates() error = %v", err)
}
if len(result.ResourceTemplates) != 1 {
t.Fatalf("ListResourceTemplates() count = %d, want 1", len(result.ResourceTemplates))
}
template := result.ResourceTemplates[0]
if template.Name != "stored_file" {
t.Fatalf("resource template name = %q, want %q", template.Name, "stored_file")
}
if template.URITemplate != "amcs://files/{id}" {
t.Fatalf("resource template uri = %q, want %q", template.URITemplate, "amcs://files/{id}")
}
}
func newStreamableTestClient(t *testing.T) *mcp.ClientSession {
t.Helper()
handler, err := New(config.MCPConfig{
ServerName: "test",
Version: "0.0.1",
SessionTimeout: time.Minute,
}, nil, streamableTestToolSet(), nil)
if err != nil {
t.Fatalf("mcpserver.New() error = %v", err)
}
httpServer := httptest.NewServer(handler)
t.Cleanup(httpServer.Close)
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "0.0.1"}, nil)
cs, err := client.Connect(context.Background(), &mcp.StreamableClientTransport{Endpoint: httpServer.URL}, nil)
if err != nil {
t.Fatalf("connect client: %v", err)
}
t.Cleanup(func() {
_ = cs.Close()
})
return cs
}