mirror of
https://gitea.com/gitea/gitea-mcp.git
synced 2026-08-03 15:49:23 +02:00
refactor!: replace mcp-go with the official MCP Go SDK
- Swap the mark3labs MCP dependency for modelcontextprotocol/go-sdk v1.7.0 - Add a declarative tool definition and JSON Schema builder to the tool package - Adapt registered tools to the official low-level handler, recovering panics and mapping failures to JSON-RPC errors - Narrow tool handlers to take a plain argument map instead of an SDK request type - Rewire stdio and HTTP transports onto the official server, moving Authorization parsing into receiving middleware - Add a golden contract test that locks the exposed tool schemas, plus SDK integration and helper tests - Add a test target and run it in the pull request workflow BREAKING CHANGE: The exported helpers change signature. Tool.RegisterRead and Tool.RegisterWrite now take tool.ServerTool instead of server.ServerTool, and the annotation constructors return *mcp.ToolAnnotations from the official SDK. Callers must build tool definitions with tool.NewDefinition and handlers with the func(context.Context, map[string]any) signature. The 30-second SSE heartbeat is removed because the official SDK has no equivalent. ServerOptions.KeepAlive is deliberately not used as a substitute, since it sends MCP ping requests and ping is removed in protocol 2026. HTTP stays stateless=false, so new clients negotiate at most 2025-11-25. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -3,7 +3,6 @@ package issue
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -17,51 +16,51 @@ import (
|
||||
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/params"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/to"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/tool"
|
||||
|
||||
gitea_sdk "gitea.dev/sdk"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
const AttachmentReadToolName = "attachment_read"
|
||||
|
||||
var AttachmentReadTool = mcp.NewTool(
|
||||
var AttachmentReadTool = tool.NewDefinition(
|
||||
AttachmentReadToolName,
|
||||
mcp.WithDescription("Read issue/comment attachments: list metadata, get metadata, or download content."),
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("Read issue or comment attachments")),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Enum("list", "get", "download")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithNumber("issue_number", mcp.Description("required for issue attachment list/get or issue-scoped metadata lookup")),
|
||||
mcp.WithNumber("comment_id", mcp.Description("required for comment attachment list/get or comment-scoped metadata lookup")),
|
||||
mcp.WithNumber("attachment_id", mcp.Description("required for get and for download when attachment_uuid is not provided")),
|
||||
mcp.WithString("attachment_uuid", mcp.Description("attachment UUID for direct download path lookup")),
|
||||
mcp.WithString("output_path", mcp.Description("write the attachment to this exact path")),
|
||||
"Read issue/comment attachments: list metadata, get metadata, or download content.",
|
||||
annotation.ReadOnly("Read issue or comment attachments"),
|
||||
tool.String("method", tool.Required(), tool.Enum("list", "get", "download")),
|
||||
tool.String("owner", tool.Required(), tool.Description(params.OwnerDesc)),
|
||||
tool.String("repo", tool.Required(), tool.Description(params.RepoDesc)),
|
||||
tool.Number("issue_number", tool.Description("required for issue attachment list/get or issue-scoped metadata lookup")),
|
||||
tool.Number("comment_id", tool.Description("required for comment attachment list/get or comment-scoped metadata lookup")),
|
||||
tool.Number("attachment_id", tool.Description("required for get and for download when attachment_uuid is not provided")),
|
||||
tool.String("attachment_uuid", tool.Description("attachment UUID for direct download path lookup")),
|
||||
tool.String("output_path", tool.Description("write the attachment to this exact path")),
|
||||
)
|
||||
|
||||
func init() {
|
||||
Tool.RegisterRead(server.ServerTool{Tool: AttachmentReadTool, Handler: attachmentReadFn})
|
||||
Tool.RegisterRead(tool.ServerTool{Tool: AttachmentReadTool, Handler: attachmentReadFn})
|
||||
}
|
||||
|
||||
func attachmentReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
method, err := params.GetString(req.GetArguments(), "method")
|
||||
func attachmentReadFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
method, err := params.GetString(args, "method")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
switch method {
|
||||
case "list":
|
||||
return listAttachmentsFn(ctx, req)
|
||||
return listAttachmentsFn(ctx, args)
|
||||
case "get":
|
||||
return getAttachmentFn(ctx, req)
|
||||
return getAttachmentFn(ctx, args)
|
||||
case "download":
|
||||
return downloadAttachmentFn(ctx, req)
|
||||
return downloadAttachmentFn(ctx, args)
|
||||
default:
|
||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||
}
|
||||
}
|
||||
|
||||
func listAttachmentsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, repo, issueNumber, commentID, err := attachmentScopeArgs(req)
|
||||
func listAttachmentsFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
owner, repo, issueNumber, commentID, err := attachmentScopeArgs(args)
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
@@ -81,29 +80,29 @@ func listAttachmentsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
|
||||
return to.TextResult(slimAttachments(attachments))
|
||||
}
|
||||
|
||||
func getAttachmentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
att, err := lookupAttachment(ctx, req)
|
||||
func getAttachmentFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
att, err := lookupAttachment(ctx, args)
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
return to.TextResult(slimAttachment(att))
|
||||
}
|
||||
|
||||
func downloadAttachmentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(req.GetArguments(), "owner")
|
||||
func downloadAttachmentFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(req.GetArguments(), "repo")
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
explicitOutputPath := params.GetOptionalString(req.GetArguments(), "output_path", "")
|
||||
attachmentUUID := strings.TrimSpace(params.GetOptionalString(req.GetArguments(), "attachment_uuid", ""))
|
||||
explicitOutputPath := params.GetOptionalString(args, "output_path", "")
|
||||
attachmentUUID := strings.TrimSpace(params.GetOptionalString(args, "attachment_uuid", ""))
|
||||
|
||||
var att *gitea_sdk.Attachment
|
||||
if attachmentUUID == "" {
|
||||
att, err = lookupAttachment(ctx, req)
|
||||
att, err = lookupAttachment(ctx, args)
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
@@ -132,7 +131,10 @@ func downloadAttachmentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
|
||||
}
|
||||
if len(limited) <= flag.MaxInlineAttachmentBytes {
|
||||
text := fmt.Sprintf("attachment %s (%s, %d bytes, %s)", name, attachmentUUID, len(limited), mimeType)
|
||||
return mcp.NewToolResultImage(text, base64.StdEncoding.EncodeToString(limited), mimeType), nil
|
||||
return &mcp.CallToolResult{Content: []mcp.Content{
|
||||
&mcp.TextContent{Text: text},
|
||||
&mcp.ImageContent{Data: limited, MIMEType: mimeType},
|
||||
}}, nil
|
||||
}
|
||||
outputPath := defaultAttachmentPath(owner, repo, name, attachmentUUID)
|
||||
if err := os.MkdirAll(filepath.Dir(outputPath), 0o700); err != nil {
|
||||
@@ -185,29 +187,29 @@ func attachmentFileResult(att *gitea_sdk.Attachment, outputPath string, written
|
||||
return to.TextResult(res)
|
||||
}
|
||||
|
||||
func attachmentScopeArgs(req mcp.CallToolRequest) (owner, repo string, issueNumber, commentID int64, err error) {
|
||||
owner, err = params.GetString(req.GetArguments(), "owner")
|
||||
func attachmentScopeArgs(args map[string]any) (owner, repo string, issueNumber, commentID int64, err error) {
|
||||
owner, err = params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return "", "", 0, 0, err
|
||||
}
|
||||
repo, err = params.GetString(req.GetArguments(), "repo")
|
||||
repo, err = params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return "", "", 0, 0, err
|
||||
}
|
||||
issueNumber = params.GetOptionalInt(req.GetArguments(), "issue_number", 0)
|
||||
commentID = params.GetOptionalInt(req.GetArguments(), "comment_id", 0)
|
||||
issueNumber = params.GetOptionalInt(args, "issue_number", 0)
|
||||
commentID = params.GetOptionalInt(args, "comment_id", 0)
|
||||
if (issueNumber > 0) == (commentID > 0) {
|
||||
return "", "", 0, 0, errors.New("exactly one of issue_number or comment_id is required")
|
||||
}
|
||||
return owner, repo, issueNumber, commentID, nil
|
||||
}
|
||||
|
||||
func lookupAttachment(ctx context.Context, req mcp.CallToolRequest) (*gitea_sdk.Attachment, error) {
|
||||
owner, repo, issueNumber, commentID, err := attachmentScopeArgs(req)
|
||||
func lookupAttachment(ctx context.Context, args map[string]any) (*gitea_sdk.Attachment, error) {
|
||||
owner, repo, issueNumber, commentID, err := attachmentScopeArgs(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
attachmentID := params.GetOptionalInt(req.GetArguments(), "attachment_id", 0)
|
||||
attachmentID := params.GetOptionalInt(args, "attachment_id", 0)
|
||||
if attachmentID <= 0 {
|
||||
return nil, errors.New("attachment_id is required")
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package issue
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -14,7 +16,7 @@ import (
|
||||
"gitea.com/gitea/gitea-mcp/pkg/flag"
|
||||
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
func TestAttachmentFilename(t *testing.T) {
|
||||
@@ -79,13 +81,13 @@ func TestAttachmentReadListIssueAttachments(t *testing.T) {
|
||||
flag.Host, flag.Token, flag.Version = server.URL, "", "test"
|
||||
defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }()
|
||||
|
||||
res, err := attachmentReadFn(context.Background(), mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{
|
||||
res, err := attachmentReadFn(context.Background(), map[string]any{
|
||||
"method": "list", "owner": owner, "repo": repo, "issue_number": float64(42),
|
||||
}}})
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("attachmentReadFn() error = %v", err)
|
||||
}
|
||||
body := res.Content[0].(mcp.TextContent).Text
|
||||
body := res.Content[0].(*mcp.TextContent).Text
|
||||
if !strings.Contains(body, `"mime_type":"image/png"`) || !strings.Contains(body, `"uuid":"uuid-1"`) {
|
||||
t.Fatalf("unexpected body: %s", body)
|
||||
}
|
||||
@@ -158,13 +160,13 @@ func TestAttachmentReadDownloadSavesLargeAttachmentToDefaultFile(t *testing.T) {
|
||||
flag.Host, flag.Token, flag.Version, flag.MaxInlineAttachmentBytes = origHost, origToken, origVersion, origInline
|
||||
}()
|
||||
|
||||
res, err := attachmentReadFn(context.Background(), mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{
|
||||
res, err := attachmentReadFn(context.Background(), map[string]any{
|
||||
"method": "download", "owner": owner, "repo": repo, "issue_number": float64(42), "attachment_id": float64(1),
|
||||
}}})
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("attachmentReadFn() error = %v", err)
|
||||
}
|
||||
body := res.Content[0].(mcp.TextContent).Text
|
||||
body := res.Content[0].(*mcp.TextContent).Text
|
||||
wantPath := filepath.Join(home, ".gitea-mcp", "attachments", owner, repo, "large-uuid-1.bin")
|
||||
if !strings.Contains(body, wantPath) {
|
||||
t.Fatalf("result missing path %q: %s", wantPath, body)
|
||||
@@ -180,3 +182,60 @@ func TestAttachmentReadDownloadSavesLargeAttachmentToDefaultFile(t *testing.T) {
|
||||
t.Fatalf("result missing bytes: %s", body)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttachmentReadDownloadReturnsRawImageContent(t *testing.T) {
|
||||
const uuid = "uuid-1"
|
||||
payload := []byte{0, 1, 2, 250}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/attachments/"+uuid {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
_, _ = w.Write(payload)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
originalHost := flag.Host
|
||||
originalLimit := flag.MaxInlineAttachmentBytes
|
||||
flag.Host = server.URL
|
||||
flag.MaxInlineAttachmentBytes = len(payload)
|
||||
defer func() {
|
||||
flag.Host = originalHost
|
||||
flag.MaxInlineAttachmentBytes = originalLimit
|
||||
}()
|
||||
|
||||
result, err := attachmentReadFn(context.Background(), map[string]any{
|
||||
"method": "download",
|
||||
"owner": "octo",
|
||||
"repo": "demo",
|
||||
"attachment_uuid": uuid,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("attachmentReadFn() error = %v", err)
|
||||
}
|
||||
if len(result.Content) != 2 {
|
||||
t.Fatalf("content count = %d, want 2", len(result.Content))
|
||||
}
|
||||
if _, ok := result.Content[0].(*mcp.TextContent); !ok {
|
||||
t.Fatalf("first content type = %T, want *mcp.TextContent", result.Content[0])
|
||||
}
|
||||
image, ok := result.Content[1].(*mcp.ImageContent)
|
||||
if !ok {
|
||||
t.Fatalf("second content type = %T, want *mcp.ImageContent", result.Content[1])
|
||||
}
|
||||
if image.MIMEType != "image/png" {
|
||||
t.Errorf("image MIME type = %q, want image/png", image.MIMEType)
|
||||
}
|
||||
if !bytes.Equal(image.Data, payload) {
|
||||
t.Errorf("image data = %v, want raw payload %v", image.Data, payload)
|
||||
}
|
||||
wire, err := json.Marshal(image)
|
||||
if err != nil {
|
||||
t.Fatalf("json.Marshal() error = %v", err)
|
||||
}
|
||||
wantBase64 := base64.StdEncoding.EncodeToString(payload)
|
||||
if !strings.Contains(string(wire), `"data":"`+wantBase64+`"`) {
|
||||
t.Errorf("wire image = %s, want base64 data %q", wire, wantBase64)
|
||||
}
|
||||
}
|
||||
|
||||
+120
-124
@@ -13,8 +13,7 @@ import (
|
||||
"gitea.com/gitea/gitea-mcp/pkg/tool"
|
||||
|
||||
gitea_sdk "gitea.dev/sdk"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// issueWithAssets / commentWithAssets wrap the SDK types to capture the
|
||||
@@ -38,125 +37,123 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
ListRepoIssuesTool = mcp.NewTool(
|
||||
ListRepoIssuesTool = tool.NewDefinition(
|
||||
ListRepoIssuesToolName,
|
||||
mcp.WithDescription("List issues in a repository (or pull requests, via the 'type' filter), filterable by state, labels, milestones, and update time range."),
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("List repository issues")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithString("state", mcp.DefaultString("all")),
|
||||
mcp.WithString("type", mcp.Description("issues or pulls"), mcp.Enum("issues", "pulls")),
|
||||
mcp.WithArray("labels", mcp.Description("label name filter"), mcp.Items(map[string]any{"type": "string"})),
|
||||
mcp.WithArray("milestones", mcp.Description("milestone name or ID filter"), mcp.Items(map[string]any{"type": "string"})),
|
||||
mcp.WithString("since", mcp.Description("updated after ISO 8601")),
|
||||
mcp.WithString("before", mcp.Description("updated before ISO 8601")),
|
||||
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)),
|
||||
mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
|
||||
"List issues in a repository (or pull requests, via the 'type' filter), filterable by state, labels, milestones, and update time range.",
|
||||
annotation.ReadOnly("List repository issues"),
|
||||
tool.String("owner", tool.Required(), tool.Description(params.OwnerDesc)),
|
||||
tool.String("repo", tool.Required(), tool.Description(params.RepoDesc)),
|
||||
tool.String("state", tool.Default("all")),
|
||||
tool.String("type", tool.Description("issues or pulls"), tool.Enum("issues", "pulls")),
|
||||
tool.Array("labels", tool.Description("label name filter"), tool.Items(map[string]any{"type": "string"})),
|
||||
tool.Array("milestones", tool.Description("milestone name or ID filter"), tool.Items(map[string]any{"type": "string"})),
|
||||
tool.String("since", tool.Description("updated after ISO 8601")),
|
||||
tool.String("before", tool.Description("updated before ISO 8601")),
|
||||
tool.Number("page", tool.Description(params.PageDesc), tool.Default(1)),
|
||||
tool.Number("per_page", tool.Description(params.PaginationDesc), tool.Default(30)),
|
||||
)
|
||||
|
||||
IssueReadTool = mcp.NewTool(
|
||||
IssueReadTool = tool.NewDefinition(
|
||||
IssueReadToolName,
|
||||
mcp.WithDescription("Read issue: details, comments, or labels."),
|
||||
mcp.WithToolAnnotation(annotation.ReadOnly("Read issue details")),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Enum("get", "get_comments", "get_labels")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithNumber("issue_number", mcp.Required()),
|
||||
"Read issue: details, comments, or labels.",
|
||||
annotation.ReadOnly("Read issue details"),
|
||||
tool.String("method", tool.Required(), tool.Enum("get", "get_comments", "get_labels")),
|
||||
tool.String("owner", tool.Required(), tool.Description(params.OwnerDesc)),
|
||||
tool.String("repo", tool.Required(), tool.Description(params.RepoDesc)),
|
||||
tool.Number("issue_number", tool.Required()),
|
||||
)
|
||||
|
||||
IssueWriteTool = mcp.NewTool(
|
||||
IssueWriteTool = tool.NewDefinition(
|
||||
IssueWriteToolName,
|
||||
mcp.WithDescription("Write issues: create, update, manage comments and labels."),
|
||||
mcp.WithToolAnnotation(annotation.Write("Create or update issues, comments, and labels")),
|
||||
mcp.WithString("method", mcp.Required(), mcp.Enum("create", "update", "add_comment", "edit_comment", "add_labels", "remove_label", "replace_labels", "clear_labels")),
|
||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||
mcp.WithNumber("issue_number", mcp.Description("required except for 'create'")),
|
||||
mcp.WithString("title", mcp.Description("required for 'create'")),
|
||||
mcp.WithString("body", mcp.Description("required for 'create'/'add_comment'/'edit_comment'")),
|
||||
mcp.WithArray("assignees", mcp.Items(map[string]any{"type": "string"})),
|
||||
mcp.WithNumber("milestone"),
|
||||
mcp.WithString("state", mcp.Enum("open", "closed", "all")),
|
||||
mcp.WithNumber("commentID", mcp.Description("for 'edit_comment'")),
|
||||
mcp.WithArray("labels", mcp.Description("label IDs"), mcp.Items(map[string]any{"type": "number"})),
|
||||
mcp.WithNumber("label_id", mcp.Description("for 'remove_label'")),
|
||||
mcp.WithString("ref", mcp.Description("branch to associate")),
|
||||
mcp.WithString("deadline", mcp.Description("ISO 8601")),
|
||||
mcp.WithBoolean("remove_deadline"),
|
||||
"Write issues: create, update, manage comments and labels.",
|
||||
annotation.Write("Create or update issues, comments, and labels"),
|
||||
tool.String("method", tool.Required(), tool.Enum("create", "update", "add_comment", "edit_comment", "add_labels", "remove_label", "replace_labels", "clear_labels")),
|
||||
tool.String("owner", tool.Required(), tool.Description(params.OwnerDesc)),
|
||||
tool.String("repo", tool.Required(), tool.Description(params.RepoDesc)),
|
||||
tool.Number("issue_number", tool.Description("required except for 'create'")),
|
||||
tool.String("title", tool.Description("required for 'create'")),
|
||||
tool.String("body", tool.Description("required for 'create'/'add_comment'/'edit_comment'")),
|
||||
tool.Array("assignees", tool.Items(map[string]any{"type": "string"})),
|
||||
tool.Number("milestone"),
|
||||
tool.String("state", tool.Enum("open", "closed", "all")),
|
||||
tool.Number("commentID", tool.Description("for 'edit_comment'")),
|
||||
tool.Array("labels", tool.Description("label IDs"), tool.Items(map[string]any{"type": "number"})),
|
||||
tool.Number("label_id", tool.Description("for 'remove_label'")),
|
||||
tool.String("ref", tool.Description("branch to associate")),
|
||||
tool.String("deadline", tool.Description("ISO 8601")),
|
||||
tool.Boolean("remove_deadline"),
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool.RegisterRead(tool.ServerTool{
|
||||
Tool: ListRepoIssuesTool,
|
||||
Handler: listRepoIssuesFn,
|
||||
})
|
||||
Tool.RegisterRead(server.ServerTool{
|
||||
Tool.RegisterRead(tool.ServerTool{
|
||||
Tool: IssueReadTool,
|
||||
Handler: issueReadFn,
|
||||
})
|
||||
Tool.RegisterWrite(server.ServerTool{
|
||||
Tool.RegisterWrite(tool.ServerTool{
|
||||
Tool: IssueWriteTool,
|
||||
Handler: issueWriteFn,
|
||||
})
|
||||
}
|
||||
|
||||
func issueReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
func issueReadFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
method, err := params.GetString(args, "method")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
switch method {
|
||||
case "get":
|
||||
return getIssueByIndexFn(ctx, req)
|
||||
return getIssueByIndexFn(ctx, args)
|
||||
case "get_comments":
|
||||
return getIssueCommentsByIndexFn(ctx, req)
|
||||
return getIssueCommentsByIndexFn(ctx, args)
|
||||
case "get_labels":
|
||||
return getIssueLabelsFn(ctx, req)
|
||||
return getIssueLabelsFn(ctx, args)
|
||||
default:
|
||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||
}
|
||||
}
|
||||
|
||||
func issueWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
args := req.GetArguments()
|
||||
func issueWriteFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
method, err := params.GetString(args, "method")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
switch method {
|
||||
case "create":
|
||||
return createIssueFn(ctx, req)
|
||||
return createIssueFn(ctx, args)
|
||||
case "update":
|
||||
return editIssueFn(ctx, req)
|
||||
return editIssueFn(ctx, args)
|
||||
case "add_comment":
|
||||
return createIssueCommentFn(ctx, req)
|
||||
return createIssueCommentFn(ctx, args)
|
||||
case "edit_comment":
|
||||
return editIssueCommentFn(ctx, req)
|
||||
return editIssueCommentFn(ctx, args)
|
||||
case "add_labels":
|
||||
return addIssueLabelsFn(ctx, req)
|
||||
return addIssueLabelsFn(ctx, args)
|
||||
case "remove_label":
|
||||
return removeIssueLabelFn(ctx, req)
|
||||
return removeIssueLabelFn(ctx, args)
|
||||
case "replace_labels":
|
||||
return replaceIssueLabelsFn(ctx, req)
|
||||
return replaceIssueLabelsFn(ctx, args)
|
||||
case "clear_labels":
|
||||
return clearIssueLabelsFn(ctx, req)
|
||||
return clearIssueLabelsFn(ctx, args)
|
||||
default:
|
||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||
}
|
||||
}
|
||||
|
||||
func getIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(req.GetArguments(), "owner")
|
||||
func getIssueByIndexFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(req.GetArguments(), "repo")
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
||||
index, err := params.GetIndex(args, "issue_number")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
@@ -170,22 +167,22 @@ func getIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
|
||||
return to.TextResult(m)
|
||||
}
|
||||
|
||||
func listRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(req.GetArguments(), "owner")
|
||||
func listRepoIssuesFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(req.GetArguments(), "repo")
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
state, ok := req.GetArguments()["state"].(string)
|
||||
state, ok := args["state"].(string)
|
||||
if !ok {
|
||||
state = "all"
|
||||
}
|
||||
labels := params.GetStringSlice(req.GetArguments(), "labels")
|
||||
milestones := params.GetStringSlice(req.GetArguments(), "milestones")
|
||||
page, pageSize := params.GetPagination(req.GetArguments(), 30)
|
||||
labels := params.GetStringSlice(args, "labels")
|
||||
milestones := params.GetStringSlice(args, "milestones")
|
||||
page, pageSize := params.GetPagination(args, 30)
|
||||
opt := gitea_sdk.ListIssueOption{
|
||||
State: gitea_sdk.StateType(state),
|
||||
Labels: labels,
|
||||
@@ -195,16 +192,16 @@ func listRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
|
||||
PageSize: pageSize,
|
||||
},
|
||||
}
|
||||
switch req.GetArguments()["type"] {
|
||||
switch args["type"] {
|
||||
case "issues":
|
||||
opt.Type = gitea_sdk.IssueTypeIssue
|
||||
case "pulls":
|
||||
opt.Type = gitea_sdk.IssueTypePull
|
||||
}
|
||||
if t := params.GetOptionalTime(req.GetArguments(), "since"); t != nil {
|
||||
if t := params.GetOptionalTime(args, "since"); t != nil {
|
||||
opt.Since = *t
|
||||
}
|
||||
if t := params.GetOptionalTime(req.GetArguments(), "before"); t != nil {
|
||||
if t := params.GetOptionalTime(args, "before"); t != nil {
|
||||
opt.Before = *t
|
||||
}
|
||||
client, err := gitea.ClientFromContext(ctx)
|
||||
@@ -218,20 +215,20 @@ func listRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
|
||||
return to.TextResult(slimIssues(issues))
|
||||
}
|
||||
|
||||
func createIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(req.GetArguments(), "owner")
|
||||
func createIssueFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(req.GetArguments(), "repo")
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
title, err := params.GetString(req.GetArguments(), "title")
|
||||
title, err := params.GetString(args, "title")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
body, err := params.GetString(req.GetArguments(), "body")
|
||||
body, err := params.GetString(args, "body")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
@@ -243,19 +240,19 @@ func createIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
|
||||
Title: title,
|
||||
Body: body,
|
||||
}
|
||||
opt.Assignees = params.GetStringSlice(req.GetArguments(), "assignees")
|
||||
if val, exists := req.GetArguments()["milestone"]; exists {
|
||||
opt.Assignees = params.GetStringSlice(args, "assignees")
|
||||
if val, exists := args["milestone"]; exists {
|
||||
if milestone, ok := params.ToInt64(val); ok {
|
||||
opt.Milestone = milestone
|
||||
}
|
||||
}
|
||||
if labelIDs, err := params.GetInt64Slice(req.GetArguments(), "labels"); err == nil {
|
||||
if labelIDs, err := params.GetInt64Slice(args, "labels"); err == nil {
|
||||
opt.Labels = labelIDs
|
||||
}
|
||||
if ref, ok := req.GetArguments()["ref"].(string); ok {
|
||||
if ref, ok := args["ref"].(string); ok {
|
||||
opt.Ref = ref
|
||||
}
|
||||
opt.Deadline = params.GetOptionalTime(req.GetArguments(), "deadline")
|
||||
opt.Deadline = params.GetOptionalTime(args, "deadline")
|
||||
issue, _, err := client.Issues.CreateIssue(ctx, owner, repo, opt)
|
||||
if err != nil {
|
||||
return to.ErrorResult(fmt.Errorf("create %v/%v/issue err: %v", owner, repo, err))
|
||||
@@ -264,20 +261,20 @@ func createIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
|
||||
return to.TextResult(slimIssue(issue))
|
||||
}
|
||||
|
||||
func createIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(req.GetArguments(), "owner")
|
||||
func createIssueCommentFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(req.GetArguments(), "repo")
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
||||
index, err := params.GetIndex(args, "issue_number")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
body, err := params.GetString(req.GetArguments(), "body")
|
||||
body, err := params.GetString(args, "body")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
@@ -296,21 +293,20 @@ func createIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
|
||||
return to.TextResult(slimComment(issueComment))
|
||||
}
|
||||
|
||||
func editIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(req.GetArguments(), "owner")
|
||||
func editIssueFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(req.GetArguments(), "repo")
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
||||
index, err := params.GetIndex(args, "issue_number")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
|
||||
args := req.GetArguments()
|
||||
opt := gitea_sdk.EditIssueOption{
|
||||
Body: params.GetPresentStringPtr(args, "body"),
|
||||
Ref: params.GetPresentStringPtr(args, "ref"),
|
||||
@@ -343,20 +339,20 @@ func editIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
|
||||
return to.TextResult(slimIssue(issue))
|
||||
}
|
||||
|
||||
func editIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(req.GetArguments(), "owner")
|
||||
func editIssueCommentFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(req.GetArguments(), "repo")
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
commentID, err := params.GetIndex(req.GetArguments(), "commentID")
|
||||
commentID, err := params.GetIndex(args, "commentID")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
body, err := params.GetString(req.GetArguments(), "body")
|
||||
body, err := params.GetString(args, "body")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
@@ -375,16 +371,16 @@ func editIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
|
||||
return to.TextResult(slimComment(issueComment))
|
||||
}
|
||||
|
||||
func getIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(req.GetArguments(), "owner")
|
||||
func getIssueCommentsByIndexFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(req.GetArguments(), "repo")
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
||||
index, err := params.GetIndex(args, "issue_number")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
@@ -402,16 +398,16 @@ func getIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*m
|
||||
return to.TextResult(out)
|
||||
}
|
||||
|
||||
func getIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(req.GetArguments(), "owner")
|
||||
func getIssueLabelsFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(req.GetArguments(), "repo")
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
||||
index, err := params.GetIndex(args, "issue_number")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
@@ -427,20 +423,20 @@ func getIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
|
||||
return to.TextResult(slim.Labels(labels))
|
||||
}
|
||||
|
||||
func addIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(req.GetArguments(), "owner")
|
||||
func addIssueLabelsFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(req.GetArguments(), "repo")
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
||||
index, err := params.GetIndex(args, "issue_number")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
labels, err := params.GetInt64Slice(req.GetArguments(), "labels")
|
||||
labels, err := params.GetInt64Slice(args, "labels")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
@@ -456,20 +452,20 @@ func addIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
|
||||
return to.TextResult(slim.Labels(issueLabels))
|
||||
}
|
||||
|
||||
func replaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(req.GetArguments(), "owner")
|
||||
func replaceIssueLabelsFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(req.GetArguments(), "repo")
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
||||
index, err := params.GetIndex(args, "issue_number")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
labels, err := params.GetInt64Slice(req.GetArguments(), "labels")
|
||||
labels, err := params.GetInt64Slice(args, "labels")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
@@ -485,16 +481,16 @@ func replaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
|
||||
return to.TextResult(slim.Labels(issueLabels))
|
||||
}
|
||||
|
||||
func clearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(req.GetArguments(), "owner")
|
||||
func clearIssueLabelsFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(req.GetArguments(), "repo")
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
||||
index, err := params.GetIndex(args, "issue_number")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
@@ -510,20 +506,20 @@ func clearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
|
||||
return to.TextResult("Labels cleared successfully")
|
||||
}
|
||||
|
||||
func removeIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(req.GetArguments(), "owner")
|
||||
func removeIssueLabelFn(ctx context.Context, args map[string]any) (*mcp.CallToolResult, error) {
|
||||
owner, err := params.GetString(args, "owner")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
repo, err := params.GetString(req.GetArguments(), "repo")
|
||||
repo, err := params.GetString(args, "repo")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
index, err := params.GetIndex(req.GetArguments(), "issue_number")
|
||||
index, err := params.GetIndex(args, "issue_number")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
labelID, err := params.GetIndex(req.GetArguments(), "label_id")
|
||||
labelID, err := params.GetIndex(args, "label_id")
|
||||
if err != nil {
|
||||
return to.ErrorResult(err)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"gitea.com/gitea/gitea-mcp/pkg/flag"
|
||||
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
func Test_listRepoIssuesFn_filters(t *testing.T) {
|
||||
@@ -60,20 +60,16 @@ func Test_listRepoIssuesFn_filters(t *testing.T) {
|
||||
flag.Version = origVersion
|
||||
}()
|
||||
|
||||
req := mcp.CallToolRequest{
|
||||
Params: mcp.CallToolParams{
|
||||
Arguments: map[string]any{
|
||||
"owner": owner,
|
||||
"repo": repo,
|
||||
"type": "issues",
|
||||
"labels": []any{"bug", "enhancement"},
|
||||
"milestones": []any{"v1.0", "2"},
|
||||
"since": "2026-01-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
args := map[string]any{
|
||||
"owner": owner,
|
||||
"repo": repo,
|
||||
"type": "issues",
|
||||
"labels": []any{"bug", "enhancement"},
|
||||
"milestones": []any{"v1.0", "2"},
|
||||
"since": "2026-01-01T00:00:00Z",
|
||||
}
|
||||
|
||||
_, err := listRepoIssuesFn(context.Background(), req)
|
||||
_, err := listRepoIssuesFn(context.Background(), args)
|
||||
if err != nil {
|
||||
t.Fatalf("listRepoIssuesFn() error = %v", err)
|
||||
}
|
||||
@@ -126,17 +122,17 @@ func Test_listRepoIssuesFn_includesMilestone(t *testing.T) {
|
||||
flag.Host, flag.Token, flag.Version = server.URL, "", "test"
|
||||
defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }()
|
||||
|
||||
req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{
|
||||
args := map[string]any{
|
||||
"owner": owner, "repo": repo,
|
||||
}}}
|
||||
res, err := listRepoIssuesFn(context.Background(), req)
|
||||
}
|
||||
res, err := listRepoIssuesFn(context.Background(), args)
|
||||
if err != nil {
|
||||
t.Fatalf("listRepoIssuesFn() error = %v", err)
|
||||
}
|
||||
if res.IsError {
|
||||
t.Fatalf("unexpected error result: %v", res.Content)
|
||||
}
|
||||
body := res.Content[0].(mcp.TextContent).Text
|
||||
body := res.Content[0].(*mcp.TextContent).Text
|
||||
if !strings.Contains(body, `"milestone"`) || !strings.Contains(body, `"v1.0"`) {
|
||||
t.Fatalf("expected milestone in list output, got: %s", body)
|
||||
}
|
||||
@@ -189,20 +185,16 @@ func Test_createIssueFn_labels(t *testing.T) {
|
||||
flag.Version = origVersion
|
||||
}()
|
||||
|
||||
req := mcp.CallToolRequest{
|
||||
Params: mcp.CallToolParams{
|
||||
Arguments: map[string]any{
|
||||
"owner": owner,
|
||||
"repo": repo,
|
||||
"title": "test issue",
|
||||
"body": "body",
|
||||
"labels": []any{float64(10), float64(20)},
|
||||
"deadline": "2026-06-01T00:00:00Z",
|
||||
},
|
||||
},
|
||||
args := map[string]any{
|
||||
"owner": owner,
|
||||
"repo": repo,
|
||||
"title": "test issue",
|
||||
"body": "body",
|
||||
"labels": []any{float64(10), float64(20)},
|
||||
"deadline": "2026-06-01T00:00:00Z",
|
||||
}
|
||||
|
||||
_, err := createIssueFn(context.Background(), req)
|
||||
_, err := createIssueFn(context.Background(), args)
|
||||
if err != nil {
|
||||
t.Fatalf("createIssueFn() error = %v", err)
|
||||
}
|
||||
@@ -255,17 +247,17 @@ func Test_getIssueByIndexFn_includesAttachments(t *testing.T) {
|
||||
flag.Host, flag.Token, flag.Version = server.URL, "", "test"
|
||||
defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }()
|
||||
|
||||
req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{
|
||||
args := map[string]any{
|
||||
"owner": owner, "repo": repo, "issue_number": float64(42),
|
||||
}}}
|
||||
res, err := getIssueByIndexFn(context.Background(), req)
|
||||
}
|
||||
res, err := getIssueByIndexFn(context.Background(), args)
|
||||
if err != nil {
|
||||
t.Fatalf("getIssueByIndexFn() error = %v", err)
|
||||
}
|
||||
if res.IsError {
|
||||
t.Fatalf("unexpected error result: %v", res.Content)
|
||||
}
|
||||
body := res.Content[0].(mcp.TextContent).Text
|
||||
body := res.Content[0].(*mcp.TextContent).Text
|
||||
if !strings.Contains(body, `[shot.png](https://example/shot.png)`) {
|
||||
t.Fatalf("expected attachment markdown inlined in body, got: %s", body)
|
||||
}
|
||||
@@ -304,17 +296,17 @@ func Test_getIssueCommentsByIndexFn_includesAttachments(t *testing.T) {
|
||||
flag.Host, flag.Token, flag.Version = server.URL, "", "test"
|
||||
defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }()
|
||||
|
||||
req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{
|
||||
args := map[string]any{
|
||||
"owner": owner, "repo": repo, "issue_number": float64(7),
|
||||
}}}
|
||||
res, err := getIssueCommentsByIndexFn(context.Background(), req)
|
||||
}
|
||||
res, err := getIssueCommentsByIndexFn(context.Background(), args)
|
||||
if err != nil {
|
||||
t.Fatalf("getIssueCommentsByIndexFn() error = %v", err)
|
||||
}
|
||||
if res.IsError {
|
||||
t.Fatalf("unexpected error result: %v", res.Content)
|
||||
}
|
||||
body := res.Content[0].(mcp.TextContent).Text
|
||||
body := res.Content[0].(*mcp.TextContent).Text
|
||||
if !strings.Contains(body, `[log.txt](https://example/log.txt)`) {
|
||||
t.Fatalf("expected attachment markdown inlined in body, got: %s", body)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user