diff --git a/dsl/mcp/cases_test.go b/dsl/mcp/cases_test.go new file mode 100644 index 00000000..178ed152 --- /dev/null +++ b/dsl/mcp/cases_test.go @@ -0,0 +1,325 @@ +package mcp + +import ( + "fmt" + "os" + "testing" + "time" + + jsoniter "github.com/json-iterator/go" + "github.com/yaoapp/gou/application" + "github.com/yaoapp/gou/model" + "github.com/yaoapp/kun/log" + "github.com/yaoapp/yao/config" + "github.com/yaoapp/yao/data" + "github.com/yaoapp/yao/dsl/types" + "github.com/yaoapp/yao/test" +) + +// systemModels system models +var systemModels = map[string]string{ + "__yao.dsl": "yao/models/dsl.mod.yao", +} + +func TestMain(m *testing.M) { + // Setup + test.Prepare(&testing.T{}, config.Conf) + defer test.Clean() + + // Load system models + model.WithCrypt([]byte(fmt.Sprintf(`{"key":"%s"}`, config.Conf.DB.AESKey)), "AES") + model.WithCrypt([]byte(`{}`), "PASSWORD") + err := loadSystemModels() + if err != nil { + log.Error("Load system models error: %s", err.Error()) + os.Exit(1) + } + + // Load application + root := os.Getenv("GOU_TEST_APPLICATION") + app, err := application.OpenFromDisk(root) // Load app + if err != nil { + log.Error("Load application error: %s", err.Error()) + os.Exit(1) + } + application.Load(app) + + // Run tests + code := m.Run() + os.Exit(code) +} + +// loadSystemModels load system models +func loadSystemModels() error { + for id, path := range systemModels { + content, err := data.Read(path) + if err != nil { + return err + } + + // Parse model + var data map[string]interface{} + err = application.Parse(path, content, &data) + if err != nil { + return err + } + + // Set prefix + if table, ok := data["table"].(map[string]interface{}); ok { + if name, ok := table["name"].(string); ok { + table["name"] = "__yao_" + name + content, err = jsoniter.Marshal(data) + if err != nil { + log.Error("failed to marshal model data: %v", err) + return fmt.Errorf("failed to marshal model data: %v", err) + } + } + } + + // Load Model + mod, err := model.LoadSource(content, id, path) + if err != nil { + log.Error("load system model %s error: %s", id, err.Error()) + return err + } + + // Drop table first + err = mod.DropTable() + if err != nil { + log.Error("drop table error: %s", err.Error()) + return err + } + + // Auto migrate + err = mod.Migrate(false, model.WithDonotInsertValues(true)) + if err != nil { + log.Error("migrate system model %s error: %s", id, err.Error()) + return err + } + } + + return nil +} + +// TestCase defines a single test case +type TestCase struct { + ID string + Source string + UpdatedSource string + Tags []string + Label string + Description string +} + +// NewTestCase creates a new test case +func NewTestCase() *TestCase { + id := getTestID() + return &TestCase{ + ID: id, + Source: fmt.Sprintf(`{ + "name": "Test MCP Client %s", + "label": "Test MCP Client", + "description": "Test MCP Client Description", + "tags": ["test_%s"], + "transport": "stdio", + "command": "echo", + "arguments": ["hello", "world"], + "env": { + "MCP_TEST": "true" + }, + "enable_sampling": true, + "enable_roots": false, + "timeout": "30s" +}`, id, id), + UpdatedSource: fmt.Sprintf(`{ + "name": "Updated MCP Client %s", + "label": "Updated MCP Client", + "description": "Updated MCP Client Description", + "tags": ["test_%s", "updated"], + "transport": "stdio", + "command": "echo", + "arguments": ["hello", "updated"], + "env": { + "MCP_TEST": "true", + "MCP_UPDATED": "true" + }, + "enable_sampling": false, + "enable_roots": true, + "timeout": "60s" +}`, id, id), + Tags: []string{fmt.Sprintf("test_%s", id)}, + Label: "Test MCP Client", + Description: "Test MCP Client Description", + } +} + +// NewHTTPTestCase creates a new HTTP test case +func NewHTTPTestCase() *TestCase { + id := getTestID() + return &TestCase{ + ID: id, + Source: fmt.Sprintf(`{ + "name": "Test HTTP MCP Client %s", + "label": "Test HTTP MCP Client", + "description": "Test HTTP MCP Client Description", + "tags": ["test_%s", "http"], + "transport": "http", + "url": "http://localhost:8080/mcp", + "authorization_token": "Bearer test-token", + "enable_sampling": true, + "enable_roots": true, + "timeout": "30s" +}`, id, id), + UpdatedSource: fmt.Sprintf(`{ + "name": "Updated HTTP MCP Client %s", + "label": "Updated HTTP MCP Client", + "description": "Updated HTTP MCP Client Description", + "tags": ["test_%s", "http", "updated"], + "transport": "http", + "url": "http://localhost:8080/mcp/v2", + "authorization_token": "Bearer updated-token", + "enable_sampling": false, + "enable_roots": false, + "timeout": "60s" +}`, id, id), + Tags: []string{fmt.Sprintf("test_%s", id), "http"}, + Label: "Test HTTP MCP Client", + Description: "Test HTTP MCP Client Description", + } +} + +// NewSSETestCase creates a new SSE test case +func NewSSETestCase() *TestCase { + id := getTestID() + return &TestCase{ + ID: id, + Source: fmt.Sprintf(`{ + "name": "Test SSE MCP Client %s", + "label": "Test SSE MCP Client", + "description": "Test SSE MCP Client Description", + "tags": ["test_%s", "sse"], + "transport": "sse", + "url": "http://localhost:8080/sse", + "authorization_token": "Bearer sse-token", + "enable_sampling": true, + "enable_elicitation": true, + "timeout": "45s" +}`, id, id), + UpdatedSource: fmt.Sprintf(`{ + "name": "Updated SSE MCP Client %s", + "label": "Updated SSE MCP Client", + "description": "Updated SSE MCP Client Description", + "tags": ["test_%s", "sse", "updated"], + "transport": "sse", + "url": "http://localhost:8080/sse/v2", + "authorization_token": "Bearer updated-sse-token", + "enable_sampling": false, + "enable_elicitation": false, + "timeout": "90s" +}`, id, id), + Tags: []string{fmt.Sprintf("test_%s", id), "sse"}, + Label: "Test SSE MCP Client", + Description: "Test SSE MCP Client Description", + } +} + +// getTestID generates a unique test ID +func getTestID() string { + return fmt.Sprintf("test_%d", time.Now().UnixNano()) +} + +// CreateOptions returns creation options +func (tc *TestCase) CreateOptions() *types.CreateOptions { + return &types.CreateOptions{ + ID: tc.ID, + Source: tc.Source, + } +} + +// LoadOptions returns load options +func (tc *TestCase) LoadOptions() *types.LoadOptions { + return &types.LoadOptions{ + ID: tc.ID, + Source: tc.Source, + } +} + +// UnloadOptions returns unload options +func (tc *TestCase) UnloadOptions() *types.UnloadOptions { + return &types.UnloadOptions{ + ID: tc.ID, + } +} + +// ReloadOptions returns reload options +func (tc *TestCase) ReloadOptions() *types.ReloadOptions { + return &types.ReloadOptions{ + ID: tc.ID, + Source: tc.UpdatedSource, + } +} + +// AssertInfo verifies if the information is correct +func (tc *TestCase) AssertInfo(info *types.Info) bool { + if info == nil { + return false + } + return info.ID == tc.ID && + info.Type == types.TypeMCPClient && + info.Label == tc.Label && + len(info.Tags) == len(tc.Tags) && + info.Description == tc.Description && + !info.Readonly && + !info.Builtin && + !info.Mtime.IsZero() && + !info.Ctime.IsZero() +} + +// AssertUpdatedInfo verifies if the updated information is correct +func (tc *TestCase) AssertUpdatedInfo(info *types.Info) bool { + if info == nil { + return false + } + expectedTags := append(tc.Tags, "updated") + return info.ID == tc.ID && + info.Type == types.TypeMCPClient && + info.Label == "Updated MCP Client" && + len(info.Tags) == len(expectedTags) && + info.Description == "Updated MCP Client Description" && + !info.Readonly && + !info.Builtin && + !info.Mtime.IsZero() && + !info.Ctime.IsZero() +} + +// AssertHTTPInfo verifies if the HTTP client information is correct +func (tc *TestCase) AssertHTTPInfo(info *types.Info) bool { + if info == nil { + return false + } + return info.ID == tc.ID && + info.Type == types.TypeMCPClient && + info.Label == "Test HTTP MCP Client" && + len(info.Tags) == 2 && // test_xxx and http + info.Description == "Test HTTP MCP Client Description" && + !info.Readonly && + !info.Builtin && + !info.Mtime.IsZero() && + !info.Ctime.IsZero() +} + +// AssertSSEInfo verifies if the SSE client information is correct +func (tc *TestCase) AssertSSEInfo(info *types.Info) bool { + if info == nil { + return false + } + return info.ID == tc.ID && + info.Type == types.TypeMCPClient && + info.Label == "Test SSE MCP Client" && + len(info.Tags) == 2 && // test_xxx and sse + info.Description == "Test SSE MCP Client Description" && + !info.Readonly && + !info.Builtin && + !info.Mtime.IsZero() && + !info.Ctime.IsZero() +} diff --git a/dsl/mcp/client.go b/dsl/mcp/client.go index 9e6e0183..e77d4a47 100644 --- a/dsl/mcp/client.go +++ b/dsl/mcp/client.go @@ -2,7 +2,9 @@ package mcp import ( "context" + "fmt" + goumcp "github.com/yaoapp/gou/mcp" "github.com/yaoapp/yao/dsl/types" ) @@ -20,30 +22,163 @@ func NewClient(root string, fs types.IO, db types.IO) types.Manager { // Loaded return all loaded DSLs func (client *YaoMCPClient) Loaded(ctx context.Context) (map[string]*types.Info, error) { - return nil, nil + infos := map[string]*types.Info{} + + // Get all loaded MCP clients + clientIDs := goumcp.ListClients() + + for _, id := range clientIDs { + // Get the client + mcpClient, err := goumcp.Select(id) + if err != nil { + continue // Skip if client not found + } + + // Get meta info from the client + meta := mcpClient.GetMetaInfo() + + infos[id] = &types.Info{ + ID: id, + Path: types.ToPath(types.TypeMCPClient, id), + Type: types.TypeMCPClient, + Label: meta.Label, + Sort: meta.Sort, + Description: meta.Description, + Tags: meta.Tags, + Readonly: meta.Readonly, + Builtin: meta.Builtin, + Mtime: meta.Mtime, + Ctime: meta.Ctime, + } + } + + return infos, nil } // Load will unload the DSL first, then load the DSL from DB or file system func (client *YaoMCPClient) Load(ctx context.Context, options *types.LoadOptions) error { + if options == nil { + return fmt.Errorf("load options is required") + } + + if options.ID == "" { + return fmt.Errorf("load options id is required") + } + + var err error + + // Case 1: If Source is provided, use LoadClientSource + if options.Source != "" { + _, err = goumcp.LoadClientSource(options.Source, options.ID) + if err != nil { + return err + } + } else if options.Path != "" && options.Store == types.StoreTypeFile { + // Case 2: If Path is provided and Store is file, use LoadClient with Path + _, err = goumcp.LoadClient(options.Path, options.ID) + if err != nil { + return err + } + } else if options.Store == types.StoreTypeDB { + // Case 3: If Store is db, get Source from DB first + if client.db == nil { + return fmt.Errorf("db io is required for store type db") + } + source, exists, err := client.db.Source(options.ID) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("mcp client %s not found in database", options.ID) + } + _, err = goumcp.LoadClientSource(source, options.ID) + if err != nil { + return err + } + } else { + // Case 4: Default case, use LoadClient with ID + path := types.ToPath(types.TypeMCPClient, options.ID) + _, err = goumcp.LoadClient(path, options.ID) + if err != nil { + return err + } + } + return nil } // Unload will unload the DSL from memory func (client *YaoMCPClient) Unload(ctx context.Context, options *types.UnloadOptions) error { + if options == nil { + return fmt.Errorf("unload options is required") + } + + if options.ID == "" { + return fmt.Errorf("unload options id is required") + } + + // Use the UnloadClient function from gou/mcp package + goumcp.UnloadClient(options.ID) return nil } // Reload will unload the DSL first, then reload the DSL from DB or file system func (client *YaoMCPClient) Reload(ctx context.Context, options *types.ReloadOptions) error { + if options == nil { + return fmt.Errorf("reload options is required") + } + + if options.ID == "" { + return fmt.Errorf("reload options id is required") + } + + // First unload + goumcp.UnloadClient(options.ID) + + // Then load + var err error + if options.Source != "" { + _, err = goumcp.LoadClientSource(options.Source, options.ID) + if err != nil { + return err + } + } else if options.Path != "" && options.Store == types.StoreTypeFile { + _, err = goumcp.LoadClient(options.Path, options.ID) + if err != nil { + return err + } + } else if options.Store == types.StoreTypeDB { + if client.db == nil { + return fmt.Errorf("db io is required for store type db") + } + source, exists, err := client.db.Source(options.ID) + if err != nil { + return err + } + if !exists { + return fmt.Errorf("mcp client %s not found in database", options.ID) + } + _, err = goumcp.LoadClientSource(source, options.ID) + if err != nil { + return err + } + } else { + path := types.ToPath(types.TypeMCPClient, options.ID) + _, err = goumcp.LoadClient(path, options.ID) + if err != nil { + return err + } + } + return nil } // Validate will validate the DSL from source func (client *YaoMCPClient) Validate(ctx context.Context, source string) (bool, []types.LintMessage) { - return false, nil + return true, []types.LintMessage{} } // Execute will execute the DSL func (client *YaoMCPClient) Execute(ctx context.Context, id string, method string, args ...any) (any, error) { - return nil, nil + return nil, fmt.Errorf("Not implemented") } diff --git a/dsl/mcp/client_test.go b/dsl/mcp/client_test.go new file mode 100644 index 00000000..228af4d7 --- /dev/null +++ b/dsl/mcp/client_test.go @@ -0,0 +1,330 @@ +package mcp + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/yao/dsl/io" + "github.com/yaoapp/yao/dsl/types" +) + +func TestMCPClientLoad(t *testing.T) { + testCase := NewTestCase() + fsio := io.NewFS(types.TypeMCPClient) + dbio := io.NewDB(types.TypeMCPClient) + manager := NewClient("mcps", fsio, dbio) + + // Test Load with nil options + err := manager.Load(context.Background(), nil) + assert.Error(t, err) + assert.Contains(t, err.Error(), "load options is required") + + // Test Load with empty ID + err = manager.Load(context.Background(), &types.LoadOptions{}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "load options id is required") + + // Test Load with Source + err = manager.Load(context.Background(), testCase.LoadOptions()) + assert.NoError(t, err) + + // Test Load from filesystem + err = fsio.Create(testCase.CreateOptions()) + assert.NoError(t, err) + + path := types.ToPath(types.TypeMCPClient, testCase.ID) + err = manager.Load(context.Background(), &types.LoadOptions{ + ID: testCase.ID, + Path: path, + Store: types.StoreTypeFile, + }) + assert.NoError(t, err) + + // Test Load from database + err = dbio.Create(testCase.CreateOptions()) + assert.NoError(t, err) + + err = manager.Load(context.Background(), &types.LoadOptions{ + ID: testCase.ID, + Store: types.StoreTypeDB, + }) + assert.NoError(t, err) + + // Clean up + err = fsio.Delete(testCase.ID) + assert.NoError(t, err) + err = dbio.Delete(testCase.ID) + assert.NoError(t, err) +} + +func TestMCPClientUnload(t *testing.T) { + testCase := NewTestCase() + fsio := io.NewFS(types.TypeMCPClient) + dbio := io.NewDB(types.TypeMCPClient) + manager := NewClient("mcps", fsio, dbio) + + // Test Unload with nil options + err := manager.Unload(context.Background(), nil) + assert.Error(t, err) + assert.Contains(t, err.Error(), "unload options is required") + + // Test Unload with empty ID + err = manager.Unload(context.Background(), &types.UnloadOptions{}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "unload options id is required") + + // Load and then unload from filesystem + err = fsio.Create(testCase.CreateOptions()) + assert.NoError(t, err) + + err = manager.Load(context.Background(), &types.LoadOptions{ + ID: testCase.ID, + Store: types.StoreTypeFile, + }) + assert.NoError(t, err) + + err = manager.Unload(context.Background(), testCase.UnloadOptions()) + assert.NoError(t, err) + + // Clean up + err = fsio.Delete(testCase.ID) + assert.NoError(t, err) +} + +func TestMCPClientReload(t *testing.T) { + testCase := NewTestCase() + fsio := io.NewFS(types.TypeMCPClient) + dbio := io.NewDB(types.TypeMCPClient) + manager := NewClient("mcps", fsio, dbio) + + // Test Reload with nil options + err := manager.Reload(context.Background(), nil) + assert.Error(t, err) + assert.Contains(t, err.Error(), "reload options is required") + + // Test Reload with empty ID + err = manager.Reload(context.Background(), &types.ReloadOptions{}) + assert.Error(t, err) + assert.Contains(t, err.Error(), "reload options id is required") + + // Load and then reload from filesystem + err = fsio.Create(testCase.CreateOptions()) + assert.NoError(t, err) + + err = manager.Load(context.Background(), &types.LoadOptions{ + ID: testCase.ID, + Store: types.StoreTypeFile, + }) + assert.NoError(t, err) + + err = manager.Reload(context.Background(), testCase.ReloadOptions()) + assert.NoError(t, err) + + // Clean up + err = fsio.Delete(testCase.ID) + assert.NoError(t, err) +} + +func TestMCPClientLoaded(t *testing.T) { + testCase := NewTestCase() + fsio := io.NewFS(types.TypeMCPClient) + dbio := io.NewDB(types.TypeMCPClient) + manager := NewClient("mcps", fsio, dbio) + + // Load from filesystem + err := fsio.Create(testCase.CreateOptions()) + assert.NoError(t, err) + + err = manager.Load(context.Background(), &types.LoadOptions{ + ID: testCase.ID, + Store: types.StoreTypeFile, + }) + assert.NoError(t, err) + + // Test Loaded + infos, err := manager.Loaded(context.Background()) + assert.NoError(t, err) + assert.NotNil(t, infos) + assert.Contains(t, infos, testCase.ID) + + // Verify metadata fields + fsInfo := infos[testCase.ID] + assert.Equal(t, testCase.ID, fsInfo.ID) + assert.Equal(t, types.TypeMCPClient, fsInfo.Type) + assert.Equal(t, testCase.Label, fsInfo.Label) + assert.Equal(t, testCase.Description, fsInfo.Description) + assert.ElementsMatch(t, testCase.Tags, fsInfo.Tags) + assert.False(t, fsInfo.Readonly) + assert.False(t, fsInfo.Builtin) + + // Clean up + err = fsio.Delete(testCase.ID) + assert.NoError(t, err) +} + +func TestMCPClientValidate(t *testing.T) { + manager := NewClient("mcps", nil, nil) + + // Test Validate + valid, messages := manager.Validate(context.Background(), "test source") + assert.True(t, valid) + assert.Empty(t, messages) +} + +func TestMCPClientExecute(t *testing.T) { + manager := NewClient("mcps", nil, nil) + + // Test Execute + result, err := manager.Execute(context.Background(), "test_id", "test_method") + assert.Error(t, err) + assert.Contains(t, err.Error(), "Not implemented") + assert.Nil(t, result) +} + +func TestMCPClientHTTPLoad(t *testing.T) { + testCase := NewHTTPTestCase() + fsio := io.NewFS(types.TypeMCPClient) + dbio := io.NewDB(types.TypeMCPClient) + manager := NewClient("mcps", fsio, dbio) + + // Test Load with HTTP Source + err := manager.Load(context.Background(), testCase.LoadOptions()) + assert.NoError(t, err) + + // Test Loaded + infos, err := manager.Loaded(context.Background()) + assert.NoError(t, err) + assert.NotNil(t, infos) + assert.Contains(t, infos, testCase.ID) + + // Verify HTTP metadata fields + httpInfo := infos[testCase.ID] + assert.Equal(t, testCase.ID, httpInfo.ID) + assert.Equal(t, types.TypeMCPClient, httpInfo.Type) + assert.Equal(t, "Test HTTP MCP Client", httpInfo.Label) + assert.Equal(t, "Test HTTP MCP Client Description", httpInfo.Description) + assert.Contains(t, httpInfo.Tags, "http") + assert.False(t, httpInfo.Readonly) + assert.False(t, httpInfo.Builtin) + + // Test Unload + err = manager.Unload(context.Background(), testCase.UnloadOptions()) + assert.NoError(t, err) +} + +func TestMCPClientSSELoad(t *testing.T) { + testCase := NewSSETestCase() + fsio := io.NewFS(types.TypeMCPClient) + dbio := io.NewDB(types.TypeMCPClient) + manager := NewClient("mcps", fsio, dbio) + + // Test Load with SSE Source + err := manager.Load(context.Background(), testCase.LoadOptions()) + assert.NoError(t, err) + + // Test Loaded + infos, err := manager.Loaded(context.Background()) + assert.NoError(t, err) + assert.NotNil(t, infos) + assert.Contains(t, infos, testCase.ID) + + // Verify SSE metadata fields + sseInfo := infos[testCase.ID] + assert.Equal(t, testCase.ID, sseInfo.ID) + assert.Equal(t, types.TypeMCPClient, sseInfo.Type) + assert.Equal(t, "Test SSE MCP Client", sseInfo.Label) + assert.Equal(t, "Test SSE MCP Client Description", sseInfo.Description) + assert.Contains(t, sseInfo.Tags, "sse") + assert.False(t, sseInfo.Readonly) + assert.False(t, sseInfo.Builtin) + + // Test Unload + err = manager.Unload(context.Background(), testCase.UnloadOptions()) + assert.NoError(t, err) +} + +func TestMCPClientLoadWithDatabaseStore(t *testing.T) { + testCase := NewTestCase() + fsio := io.NewFS(types.TypeMCPClient) + dbio := io.NewDB(types.TypeMCPClient) + manager := NewClient("mcps", fsio, dbio) + + // Create in database first + err := dbio.Create(testCase.CreateOptions()) + assert.NoError(t, err) + + // Test Load from database + err = manager.Load(context.Background(), &types.LoadOptions{ + ID: testCase.ID, + Store: types.StoreTypeDB, + }) + assert.NoError(t, err) + + // Test Loaded + infos, err := manager.Loaded(context.Background()) + assert.NoError(t, err) + assert.NotNil(t, infos) + assert.Contains(t, infos, testCase.ID) + + // Verify metadata fields + dbInfo := infos[testCase.ID] + assert.Equal(t, testCase.ID, dbInfo.ID) + assert.Equal(t, types.TypeMCPClient, dbInfo.Type) + assert.Equal(t, testCase.Label, dbInfo.Label) + assert.Equal(t, testCase.Description, dbInfo.Description) + assert.ElementsMatch(t, testCase.Tags, dbInfo.Tags) + assert.False(t, dbInfo.Readonly) + assert.False(t, dbInfo.Builtin) + + // Test Reload from database + err = manager.Reload(context.Background(), &types.ReloadOptions{ + ID: testCase.ID, + Source: testCase.UpdatedSource, + Store: types.StoreTypeDB, + }) + assert.NoError(t, err) + + // Clean up + err = dbio.Delete(testCase.ID) + assert.NoError(t, err) +} + +func TestMCPClientLoadWithFileStore(t *testing.T) { + testCase := NewTestCase() + fsio := io.NewFS(types.TypeMCPClient) + dbio := io.NewDB(types.TypeMCPClient) + manager := NewClient("mcps", fsio, dbio) + + // Create in file system first + err := fsio.Create(testCase.CreateOptions()) + assert.NoError(t, err) + + // Test Load from file system with explicit path + path := types.ToPath(types.TypeMCPClient, testCase.ID) + err = manager.Load(context.Background(), &types.LoadOptions{ + ID: testCase.ID, + Path: path, + Store: types.StoreTypeFile, + }) + assert.NoError(t, err) + + // Test Loaded + infos, err := manager.Loaded(context.Background()) + assert.NoError(t, err) + assert.NotNil(t, infos) + assert.Contains(t, infos, testCase.ID) + + // Test Reload from file system + err = manager.Reload(context.Background(), &types.ReloadOptions{ + ID: testCase.ID, + Path: path, + Source: testCase.UpdatedSource, + Store: types.StoreTypeFile, + }) + assert.NoError(t, err) + + // Clean up + err = fsio.Delete(testCase.ID) + assert.NoError(t, err) +} diff --git a/go.mod b/go.mod index 5cd8ef06..6350afbc 100644 --- a/go.mod +++ b/go.mod @@ -96,6 +96,7 @@ require ( github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lib/pq v1.10.9 // indirect + github.com/mark3labs/mcp-go v0.32.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-sqlite3 v1.14.28 // indirect @@ -131,6 +132,7 @@ require ( github.com/xdg-go/stringprep v1.0.4 // indirect github.com/xuri/efp v0.0.1 // indirect github.com/xuri/nfp v0.0.1 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.mongodb.org/mongo-driver v1.17.3 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect diff --git a/go.sum b/go.sum index 3e5bd997..2a0f960d 100644 --- a/go.sum +++ b/go.sum @@ -181,6 +181,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7MU8= +github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= @@ -300,6 +302,8 @@ github.com/xuri/excelize/v2 v2.9.1 h1:VdSGk+rraGmgLHGFaGG9/9IWu1nj4ufjJ7uwMDtj8Q github.com/xuri/excelize/v2 v2.9.1/go.mod h1:x7L6pKz2dvo9ejrRuD8Lnl98z4JLt0TGAwjhW+EiP8s= github.com/xuri/nfp v0.0.1 h1:MDamSGatIvp8uOmDP8FnmjuQpu90NzdJxo7242ANR9Q= github.com/xuri/nfp v0.0.1/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=