package wasmguest import ( "encoding/json" "testing" "git.dev.alexdunmow.com/block/pluginsdk/rbac" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/reflect/protodesc" "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/descriptorpb" _ "google.golang.org/protobuf/types/known/timestamppb" ) func TestCaptureMCPToolsUsesCompiledProtobufDescriptor(t *testing.T) { registerMCPFixture(t) procedure := "/mcpfixture.v1.WikiService/ListArticles" tools := captureMCPTools(map[string]rbac.Role{procedure: rbac.RoleAdmin}) if len(tools) != 1 { t.Fatalf("captureMCPTools() returned %d tools, want 1", len(tools)) } tool := tools[0] if tool.GetProcedure() != procedure || tool.GetServiceFullName() != "mcpfixture.v1.WikiService" || tool.GetMethodName() != "ListArticles" { t.Fatalf("unexpected procedure metadata: %#v", tool) } if tool.GetDescription() != "Lists Wiki articles." || tool.GetDocumentation() != "ListArticles lists Wiki articles.\n\nSupports pagination." { t.Fatalf("unexpected descriptor documentation: description=%q doc=%q", tool.GetDescription(), tool.GetDocumentation()) } var schema map[string]any if err := json.Unmarshal(tool.GetInputSchemaJson(), &schema); err != nil { t.Fatalf("schema is not JSON: %v", err) } properties, ok := schema["properties"].(map[string]any) if !ok { t.Fatalf("schema properties = %#v", schema["properties"]) } assertSchemaType(t, properties, "search", "string") assertSchemaType(t, properties, "page", "integer") assertSchemaType(t, properties, "published_after", "string") } func TestCaptureMCPToolsSkipsUnresolvableAndStreamingProcedures(t *testing.T) { registerMCPFixture(t) tools := captureMCPTools(map[string]rbac.Role{ "/mcpfixture.v1.WikiService/StreamArticles": rbac.RoleAdmin, "/missing.v1.WikiService/ListArticles": rbac.RoleAdmin, "not-a-procedure": rbac.RoleAdmin, }) if len(tools) != 0 { t.Fatalf("captureMCPTools() returned %d tools, want fail-closed omission", len(tools)) } } func assertSchemaType(t *testing.T, properties map[string]any, field, expected string) { t.Helper() property, ok := properties[field].(map[string]any) if !ok || property["type"] != expected { t.Fatalf("schema field %q = %#v, want type %q", field, properties[field], expected) } } func registerMCPFixture(t *testing.T) { t.Helper() if _, err := protoregistry.GlobalFiles.FindFileByPath("test/mcpfixture.proto"); err == nil { return } file, err := protodesc.NewFile(&descriptorpb.FileDescriptorProto{ Name: new("test/mcpfixture.proto"), Package: new("mcpfixture.v1"), Syntax: new("proto3"), Dependency: []string{"google/protobuf/timestamp.proto"}, MessageType: []*descriptorpb.DescriptorProto{{ Name: new("ListArticlesRequest"), Field: []*descriptorpb.FieldDescriptorProto{ {Name: new("search"), Number: proto.Int32(1), Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), Type: descriptorpb.FieldDescriptorProto_TYPE_STRING.Enum()}, {Name: new("page"), Number: proto.Int32(2), Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), Type: descriptorpb.FieldDescriptorProto_TYPE_INT32.Enum()}, {Name: new("published_after"), Number: proto.Int32(3), Label: descriptorpb.FieldDescriptorProto_LABEL_OPTIONAL.Enum(), Type: descriptorpb.FieldDescriptorProto_TYPE_MESSAGE.Enum(), TypeName: new(".google.protobuf.Timestamp")}, }, }, { Name: new("ListArticlesResponse"), }}, Service: []*descriptorpb.ServiceDescriptorProto{{ Name: new("WikiService"), Method: []*descriptorpb.MethodDescriptorProto{{ Name: new("ListArticles"), InputType: new(".mcpfixture.v1.ListArticlesRequest"), OutputType: new(".mcpfixture.v1.ListArticlesResponse"), }, { Name: new("StreamArticles"), InputType: new(".mcpfixture.v1.ListArticlesRequest"), OutputType: new(".mcpfixture.v1.ListArticlesResponse"), ServerStreaming: new(true), }}, }}, SourceCodeInfo: &descriptorpb.SourceCodeInfo{Location: []*descriptorpb.SourceCodeInfo_Location{{ Path: []int32{6, 0, 2, 0}, Span: []int32{1, 0, 1, 1}, LeadingComments: new("ListArticles lists Wiki articles.\n\nSupports pagination."), }}}, }, protoregistry.GlobalFiles) if err != nil { t.Fatalf("create fixture descriptor: %v", err) } if err := protoregistry.GlobalFiles.RegisterFile(file); err != nil { t.Fatalf("register fixture descriptor: %v", err) } }