55 "os"
66 "path/filepath"
77 "strings"
8+ "sync"
89 "testing"
910
1011 "github.com/modelcontextprotocol/go-sdk/mcp"
@@ -69,10 +70,11 @@ func (m *mockClient) ShouldInstall(ctx context.Context) (bool, error) {
6970 return true , nil
7071}
7172
72- func (m * mockClient ) addSkill (name , description , content , baseDir string ) {
73+ func (m * mockClient ) addSkill (name , description , version , content , baseDir string ) {
7374 m .skills [name ] = & clients.SkillContent {
7475 Name : name ,
7576 Description : description ,
77+ Version : version ,
7678 Content : content ,
7779 BaseDir : baseDir ,
7880 }
@@ -81,7 +83,7 @@ func (m *mockClient) addSkill(name, description, content, baseDir string) {
8183func TestServer_ReadSkill (t * testing.T ) {
8284 // Create a mock client with test skills
8385 mock := newMockClient ()
84- mock .addSkill ("test-skill" , "A test skill" , "# Test Skill\n \n This is the skill content." , "/tmp/skills/test-skill" )
86+ mock .addSkill ("test-skill" , "A test skill" , "1.0.0" , " # Test Skill\n \n This is the skill content." , "/tmp/skills/test-skill" )
8587
8688 // Create a registry with just our mock client
8789 registry := clients .NewRegistry ()
@@ -222,7 +224,7 @@ This skill helps with @example.txt integration testing.
222224
223225 // Use a mock client that reads from our temp directory
224226 mock := newMockClient ()
225- mock .addSkill ("integration-skill" , "An integration test skill" , skillContent , skillDir )
227+ mock .addSkill ("integration-skill" , "An integration test skill" , "1.0.0" , skillContent , skillDir )
226228
227229 registry := clients .NewRegistry ()
228230 registry .Register (mock )
@@ -338,3 +340,103 @@ func TestResolveFileReferences(t *testing.T) {
338340 })
339341 }
340342}
343+
344+ // mockUsageReporter captures usage reports for testing
345+ type mockUsageReporter struct {
346+ mu sync.Mutex
347+ reports []usageReport
348+ called chan struct {}
349+ }
350+
351+ type usageReport struct {
352+ skillName string
353+ skillVersion string
354+ }
355+
356+ func newMockUsageReporter () * mockUsageReporter {
357+ return & mockUsageReporter {
358+ called : make (chan struct {}, 1 ),
359+ }
360+ }
361+
362+ func (m * mockUsageReporter ) ReportSkillUsage (skillName , skillVersion string ) {
363+ m .mu .Lock ()
364+ m .reports = append (m .reports , usageReport {skillName , skillVersion })
365+ m .mu .Unlock ()
366+ select {
367+ case m .called <- struct {}{}:
368+ default :
369+ }
370+ }
371+
372+ func (m * mockUsageReporter ) getReports () []usageReport {
373+ m .mu .Lock ()
374+ defer m .mu .Unlock ()
375+ return append ([]usageReport {}, m .reports ... )
376+ }
377+
378+ func TestServer_ReadSkill_ReportsUsage (t * testing.T ) {
379+ // Create a mock client with a test skill
380+ mock := newMockClient ()
381+ mock .addSkill ("usage-test-skill" , "A skill for testing usage" , "2.0.0" , "# Usage Test\n \n Content here." , "/tmp/skills/usage-test" )
382+
383+ registry := clients .NewRegistry ()
384+ registry .Register (mock )
385+
386+ server := NewServer (registry )
387+
388+ // Inject mock usage reporter
389+ mockReporter := newMockUsageReporter ()
390+ server .SetUsageReporter (mockReporter )
391+
392+ // Create and connect MCP server
393+ impl := & mcp.Implementation {Name : "skills" , Version : "1.0.0" }
394+ mcpServer := mcp .NewServer (impl , nil )
395+ mcp .AddTool (mcpServer , & mcp.Tool {
396+ Name : "read_skill" ,
397+ Description : "Read a skill's full instructions and content." ,
398+ }, server .handleReadSkill )
399+
400+ ctx := context .Background ()
401+ t1 , t2 := mcp .NewInMemoryTransports ()
402+
403+ _ , err := mcpServer .Connect (ctx , t1 , nil )
404+ if err != nil {
405+ t .Fatalf ("Failed to connect server: %v" , err )
406+ }
407+
408+ client := mcp .NewClient (& mcp.Implementation {Name : "test-client" , Version : "v1.0.0" }, nil )
409+ session , err := client .Connect (ctx , t2 , nil )
410+ if err != nil {
411+ t .Fatalf ("Failed to connect client: %v" , err )
412+ }
413+ defer session .Close ()
414+
415+ // Read the skill
416+ result , err := session .CallTool (ctx , & mcp.CallToolParams {
417+ Name : "read_skill" ,
418+ Arguments : map [string ]any {"name" : "usage-test-skill" },
419+ })
420+ if err != nil {
421+ t .Fatalf ("CallTool failed: %v" , err )
422+ }
423+ if result .IsError {
424+ t .Fatalf ("Tool returned error: %v" , result .Content )
425+ }
426+
427+ // Wait for the goroutine to call the reporter
428+ <- mockReporter .called
429+
430+ // Verify the usage was reported
431+ reports := mockReporter .getReports ()
432+ if len (reports ) != 1 {
433+ t .Fatalf ("Expected 1 usage report, got %d" , len (reports ))
434+ }
435+
436+ if reports [0 ].skillName != "usage-test-skill" {
437+ t .Errorf ("Expected skill name 'usage-test-skill', got %q" , reports [0 ].skillName )
438+ }
439+ if reports [0 ].skillVersion != "2.0.0" {
440+ t .Errorf ("Expected skill version '2.0.0', got %q" , reports [0 ].skillVersion )
441+ }
442+ }
0 commit comments