Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit73dcb46

Browse files
authored
Add get_release_by_tag tool (#938)
* add get_release_by_tag tool* add tool* add tests* autogen* remove comment
1 parent2621dbe commit73dcb46

File tree

5 files changed

+267
-0
lines changed

5 files changed

+267
-0
lines changed

‎README.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,6 +846,11 @@ The following sets of tools are available (all are on by default):
846846
-`owner`: Repository owner (string, required)
847847
-`repo`: Repository name (string, required)
848848

849+
-**get_release_by_tag** - Get a release by tag name
850+
-`owner`: Repository owner (string, required)
851+
-`repo`: Repository name (string, required)
852+
-`tag`: Tag name (e.g., 'v1.0.0') (string, required)
853+
849854
-**get_tag** - Get tag details
850855
-`owner`: Repository owner (string, required)
851856
-`repo`: Repository name (string, required)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"annotations": {
3+
"title":"Get a release by tag name",
4+
"readOnlyHint":true
5+
},
6+
"description":"Get a specific release by its tag name in a GitHub repository",
7+
"inputSchema": {
8+
"properties": {
9+
"owner": {
10+
"description":"Repository owner",
11+
"type":"string"
12+
},
13+
"repo": {
14+
"description":"Repository name",
15+
"type":"string"
16+
},
17+
"tag": {
18+
"description":"Tag name (e.g., 'v1.0.0')",
19+
"type":"string"
20+
}
21+
},
22+
"required": [
23+
"owner",
24+
"repo",
25+
"tag"
26+
],
27+
"type":"object"
28+
},
29+
"name":"get_release_by_tag"
30+
}

‎pkg/github/repositories.go‎

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,6 +1441,72 @@ func GetLatestRelease(getClient GetClientFn, t translations.TranslationHelperFun
14411441
}
14421442
}
14431443

1444+
funcGetReleaseByTag(getClientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
1445+
returnmcp.NewTool("get_release_by_tag",
1446+
mcp.WithDescription(t("TOOL_GET_RELEASE_BY_TAG_DESCRIPTION","Get a specific release by its tag name in a GitHub repository")),
1447+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
1448+
Title:t("TOOL_GET_RELEASE_BY_TAG_USER_TITLE","Get a release by tag name"),
1449+
ReadOnlyHint:ToBoolPtr(true),
1450+
}),
1451+
mcp.WithString("owner",
1452+
mcp.Required(),
1453+
mcp.Description("Repository owner"),
1454+
),
1455+
mcp.WithString("repo",
1456+
mcp.Required(),
1457+
mcp.Description("Repository name"),
1458+
),
1459+
mcp.WithString("tag",
1460+
mcp.Required(),
1461+
mcp.Description("Tag name (e.g., 'v1.0.0')"),
1462+
),
1463+
),
1464+
func(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
1465+
owner,err:=RequiredParam[string](request,"owner")
1466+
iferr!=nil {
1467+
returnmcp.NewToolResultError(err.Error()),nil
1468+
}
1469+
repo,err:=RequiredParam[string](request,"repo")
1470+
iferr!=nil {
1471+
returnmcp.NewToolResultError(err.Error()),nil
1472+
}
1473+
tag,err:=RequiredParam[string](request,"tag")
1474+
iferr!=nil {
1475+
returnmcp.NewToolResultError(err.Error()),nil
1476+
}
1477+
1478+
client,err:=getClient(ctx)
1479+
iferr!=nil {
1480+
returnnil,fmt.Errorf("failed to get GitHub client: %w",err)
1481+
}
1482+
1483+
release,resp,err:=client.Repositories.GetReleaseByTag(ctx,owner,repo,tag)
1484+
iferr!=nil {
1485+
returnghErrors.NewGitHubAPIErrorResponse(ctx,
1486+
fmt.Sprintf("failed to get release by tag: %s",tag),
1487+
resp,
1488+
err,
1489+
),nil
1490+
}
1491+
deferfunc() {_=resp.Body.Close() }()
1492+
1493+
ifresp.StatusCode!=http.StatusOK {
1494+
body,err:=io.ReadAll(resp.Body)
1495+
iferr!=nil {
1496+
returnnil,fmt.Errorf("failed to read response body: %w",err)
1497+
}
1498+
returnmcp.NewToolResultError(fmt.Sprintf("failed to get release by tag: %s",string(body))),nil
1499+
}
1500+
1501+
r,err:=json.Marshal(release)
1502+
iferr!=nil {
1503+
returnnil,fmt.Errorf("failed to marshal response: %w",err)
1504+
}
1505+
1506+
returnmcp.NewToolResultText(string(r)),nil
1507+
}
1508+
}
1509+
14441510
// filterPaths filters the entries in a GitHub tree to find paths that
14451511
// match the given suffix.
14461512
// maxResults limits the number of results returned to first maxResults entries,

‎pkg/github/repositories_test.go‎

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2287,6 +2287,171 @@ func Test_GetLatestRelease(t *testing.T) {
22872287
}
22882288
}
22892289

2290+
funcTest_GetReleaseByTag(t*testing.T) {
2291+
mockClient:=github.NewClient(nil)
2292+
tool,_:=GetReleaseByTag(stubGetClientFn(mockClient),translations.NullTranslationHelper)
2293+
require.NoError(t,toolsnaps.Test(tool.Name,tool))
2294+
2295+
assert.Equal(t,"get_release_by_tag",tool.Name)
2296+
assert.NotEmpty(t,tool.Description)
2297+
assert.Contains(t,tool.InputSchema.Properties,"owner")
2298+
assert.Contains(t,tool.InputSchema.Properties,"repo")
2299+
assert.Contains(t,tool.InputSchema.Properties,"tag")
2300+
assert.ElementsMatch(t,tool.InputSchema.Required, []string{"owner","repo","tag"})
2301+
2302+
mockRelease:=&github.RepositoryRelease{
2303+
ID:github.Ptr(int64(1)),
2304+
TagName:github.Ptr("v1.0.0"),
2305+
Name:github.Ptr("Release v1.0.0"),
2306+
Body:github.Ptr("This is the first stable release."),
2307+
Assets: []*github.ReleaseAsset{
2308+
{
2309+
ID:github.Ptr(int64(1)),
2310+
Name:github.Ptr("release-v1.0.0.tar.gz"),
2311+
},
2312+
},
2313+
}
2314+
2315+
tests:= []struct {
2316+
namestring
2317+
mockedClient*http.Client
2318+
requestArgsmap[string]interface{}
2319+
expectErrorbool
2320+
expectedResult*github.RepositoryRelease
2321+
expectedErrMsgstring
2322+
}{
2323+
{
2324+
name:"successful release by tag fetch",
2325+
mockedClient:mock.NewMockedHTTPClient(
2326+
mock.WithRequestMatch(
2327+
mock.GetReposReleasesTagsByOwnerByRepoByTag,
2328+
mockRelease,
2329+
),
2330+
),
2331+
requestArgs:map[string]interface{}{
2332+
"owner":"owner",
2333+
"repo":"repo",
2334+
"tag":"v1.0.0",
2335+
},
2336+
expectError:false,
2337+
expectedResult:mockRelease,
2338+
},
2339+
{
2340+
name:"missing owner parameter",
2341+
mockedClient:mock.NewMockedHTTPClient(),
2342+
requestArgs:map[string]interface{}{
2343+
"repo":"repo",
2344+
"tag":"v1.0.0",
2345+
},
2346+
expectError:false,// Returns tool error, not Go error
2347+
expectedErrMsg:"missing required parameter: owner",
2348+
},
2349+
{
2350+
name:"missing repo parameter",
2351+
mockedClient:mock.NewMockedHTTPClient(),
2352+
requestArgs:map[string]interface{}{
2353+
"owner":"owner",
2354+
"tag":"v1.0.0",
2355+
},
2356+
expectError:false,// Returns tool error, not Go error
2357+
expectedErrMsg:"missing required parameter: repo",
2358+
},
2359+
{
2360+
name:"missing tag parameter",
2361+
mockedClient:mock.NewMockedHTTPClient(),
2362+
requestArgs:map[string]interface{}{
2363+
"owner":"owner",
2364+
"repo":"repo",
2365+
},
2366+
expectError:false,// Returns tool error, not Go error
2367+
expectedErrMsg:"missing required parameter: tag",
2368+
},
2369+
{
2370+
name:"release by tag not found",
2371+
mockedClient:mock.NewMockedHTTPClient(
2372+
mock.WithRequestMatchHandler(
2373+
mock.GetReposReleasesTagsByOwnerByRepoByTag,
2374+
http.HandlerFunc(func(w http.ResponseWriter,_*http.Request) {
2375+
w.WriteHeader(http.StatusNotFound)
2376+
_,_=w.Write([]byte(`{"message": "Not Found"}`))
2377+
}),
2378+
),
2379+
),
2380+
requestArgs:map[string]interface{}{
2381+
"owner":"owner",
2382+
"repo":"repo",
2383+
"tag":"v999.0.0",
2384+
},
2385+
expectError:false,// API errors return tool errors, not Go errors
2386+
expectedErrMsg:"failed to get release by tag: v999.0.0",
2387+
},
2388+
{
2389+
name:"server error",
2390+
mockedClient:mock.NewMockedHTTPClient(
2391+
mock.WithRequestMatchHandler(
2392+
mock.GetReposReleasesTagsByOwnerByRepoByTag,
2393+
http.HandlerFunc(func(w http.ResponseWriter,_*http.Request) {
2394+
w.WriteHeader(http.StatusInternalServerError)
2395+
_,_=w.Write([]byte(`{"message": "Internal Server Error"}`))
2396+
}),
2397+
),
2398+
),
2399+
requestArgs:map[string]interface{}{
2400+
"owner":"owner",
2401+
"repo":"repo",
2402+
"tag":"v1.0.0",
2403+
},
2404+
expectError:false,// API errors return tool errors, not Go errors
2405+
expectedErrMsg:"failed to get release by tag: v1.0.0",
2406+
},
2407+
}
2408+
2409+
for_,tc:=rangetests {
2410+
t.Run(tc.name,func(t*testing.T) {
2411+
client:=github.NewClient(tc.mockedClient)
2412+
_,handler:=GetReleaseByTag(stubGetClientFn(client),translations.NullTranslationHelper)
2413+
2414+
request:=createMCPRequest(tc.requestArgs)
2415+
2416+
result,err:=handler(context.Background(),request)
2417+
2418+
iftc.expectError {
2419+
require.Error(t,err)
2420+
assert.Contains(t,err.Error(),tc.expectedErrMsg)
2421+
return
2422+
}
2423+
2424+
require.NoError(t,err)
2425+
2426+
iftc.expectedErrMsg!="" {
2427+
require.True(t,result.IsError)
2428+
errorContent:=getErrorResult(t,result)
2429+
assert.Contains(t,errorContent.Text,tc.expectedErrMsg)
2430+
return
2431+
}
2432+
2433+
require.False(t,result.IsError)
2434+
2435+
textContent:=getTextResult(t,result)
2436+
2437+
varreturnedRelease github.RepositoryRelease
2438+
err=json.Unmarshal([]byte(textContent.Text),&returnedRelease)
2439+
require.NoError(t,err)
2440+
2441+
assert.Equal(t,*tc.expectedResult.ID,*returnedRelease.ID)
2442+
assert.Equal(t,*tc.expectedResult.TagName,*returnedRelease.TagName)
2443+
assert.Equal(t,*tc.expectedResult.Name,*returnedRelease.Name)
2444+
iftc.expectedResult.Body!=nil {
2445+
assert.Equal(t,*tc.expectedResult.Body,*returnedRelease.Body)
2446+
}
2447+
iflen(tc.expectedResult.Assets)>0 {
2448+
require.Len(t,returnedRelease.Assets,len(tc.expectedResult.Assets))
2449+
assert.Equal(t,*tc.expectedResult.Assets[0].Name,*returnedRelease.Assets[0].Name)
2450+
}
2451+
})
2452+
}
2453+
}
2454+
22902455
funcTest_filterPaths(t*testing.T) {
22912456
tests:= []struct {
22922457
namestring

‎pkg/github/tools.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
3333
toolsets.NewServerTool(GetTag(getClient,t)),
3434
toolsets.NewServerTool(ListReleases(getClient,t)),
3535
toolsets.NewServerTool(GetLatestRelease(getClient,t)),
36+
toolsets.NewServerTool(GetReleaseByTag(getClient,t)),
3637
).
3738
AddWriteTools(
3839
toolsets.NewServerTool(CreateOrUpdateFile(getClient,t)),

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp