diff --git a/cmd/gitea-mcp/main.go b/cmd/gitea-mcp/main.go index 6d1e976..eaf27bd 100644 --- a/cmd/gitea-mcp/main.go +++ b/cmd/gitea-mcp/main.go @@ -28,6 +28,7 @@ func main() { reg := registry.New() reg.Register(tools.NewRepoList(giteaClient, ownerAllow)) + reg.Register(tools.NewRepoGet(giteaClient, ownerAllow)) mcpSrv := mcp.NewServer(mcp.ServerOptions{ Registry: reg, diff --git a/internal/tools/repo_get.go b/internal/tools/repo_get.go new file mode 100644 index 0000000..b7fb650 --- /dev/null +++ b/internal/tools/repo_get.go @@ -0,0 +1,49 @@ +package tools + +import ( + "context" + "encoding/json" + + "gitea.d-ma.be/mathias/gitea-mcp/internal/allowlist" + "gitea.d-ma.be/mathias/gitea-mcp/internal/gitea" + "gitea.d-ma.be/mathias/gitea-mcp/internal/registry" +) + +type RepoGet struct { + c *gitea.Client + a *allowlist.Allowlist +} + +func NewRepoGet(c *gitea.Client, a *allowlist.Allowlist) *RepoGet { return &RepoGet{c: c, a: a} } + +func (t *RepoGet) Descriptor() registry.ToolDescriptor { + return registry.ToolDescriptor{ + Name: "repo_get", + Description: "Get a repo's metadata.", + InputSchema: json.RawMessage(`{ + "type":"object", + "properties":{"owner":{"type":"string"},"name":{"type":"string"}}, + "required":["owner","name"] + }`), + } +} + +type repoGetArgs struct { + Owner string `json:"owner"` + Name string `json:"name"` +} + +func (t *RepoGet) Call(ctx context.Context, raw json.RawMessage) (json.RawMessage, error) { + var args repoGetArgs + if err := parseArgs(raw, &args); err != nil { + return nil, err + } + if err := t.a.Check(args.Owner); err != nil { + return nil, err + } + r, err := t.c.GetRepo(ctx, args.Owner, args.Name) + if err != nil { + return nil, err + } + return textOK(r) +} diff --git a/internal/tools/repo_get_test.go b/internal/tools/repo_get_test.go new file mode 100644 index 0000000..52817a2 --- /dev/null +++ b/internal/tools/repo_get_test.go @@ -0,0 +1,36 @@ +package tools_test + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "gitea.d-ma.be/mathias/gitea-mcp/internal/allowlist" + "gitea.d-ma.be/mathias/gitea-mcp/internal/gitea" + "gitea.d-ma.be/mathias/gitea-mcp/internal/tools" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRepoGetTool(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/api/v1/repos/mathias/infra", r.URL.Path) + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"name":"infra","full_name":"mathias/infra","default_branch":"main","private":true}`)) + })) + defer srv.Close() + + tool := tools.NewRepoGet(gitea.NewClient(srv.URL, "tok"), allowlist.New([]string{"mathias"})) + out, err := tool.Call(context.Background(), json.RawMessage(`{"owner":"mathias","name":"infra"}`)) + require.NoError(t, err) + assert.Contains(t, string(out), `"full_name":"mathias/infra"`) + assert.Contains(t, string(out), `"default_branch":"main"`) +} + +func TestRepoGetAllowlistRejects(t *testing.T) { + tool := tools.NewRepoGet(gitea.NewClient("http://unused", ""), allowlist.New([]string{"mathias"})) + _, err := tool.Call(context.Background(), json.RawMessage(`{"owner":"evil","name":"x"}`)) + require.Error(t, err) +}