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

Commitdeb66ce

Browse files
committed
feat: add support for running workflows by ID and filename in GitHub Actions tools
- Introduced a new tool, RunWorkflowByFileName, to allow users to run workflows using the workflow filename.- Updated the existing RunWorkflow tool to accept a numeric workflow ID instead of a filename.- Enhanced tests to cover scenarios for both running workflows by ID and filename, including error handling for missing parameters.- Improved tool descriptions for clarity and usability.
1 parent1339249 commitdeb66ce

File tree

3 files changed

+185
-10
lines changed

3 files changed

+185
-10
lines changed

‎pkg/github/actions.go

Lines changed: 98 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -229,24 +229,24 @@ func ListWorkflowRuns(getClient GetClientFn, t translations.TranslationHelperFun
229229
}
230230
}
231231

232-
// RunWorkflow creates a tool to run an Actions workflow
232+
// RunWorkflow creates a tool to run an Actions workflow by workflow ID
233233
funcRunWorkflow(getClientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
234234
returnmcp.NewTool("run_workflow",
235-
mcp.WithDescription(t("TOOL_RUN_WORKFLOW_DESCRIPTION","Run an Actions workflow")),
235+
mcp.WithDescription(t("TOOL_RUN_WORKFLOW_DESCRIPTION","Run an Actions workflow by workflow ID")),
236236
mcp.WithToolAnnotation(mcp.ToolAnnotation{
237237
ReadOnlyHint:ToBoolPtr(false),
238238
}),
239239
mcp.WithString("owner",
240240
mcp.Required(),
241-
mcp.Description("The accountowner of the repository. The name is not case sensitive."),
241+
mcp.Description("Repositoryowner"),
242242
),
243243
mcp.WithString("repo",
244244
mcp.Required(),
245245
mcp.Description("Repository name"),
246246
),
247-
mcp.WithString("workflow_file",
247+
mcp.WithNumber("workflow_id",
248248
mcp.Required(),
249-
mcp.Description("The workflow IDor workflow file name"),
249+
mcp.Description("The workflow ID(numeric identifier)"),
250250
),
251251
mcp.WithString("ref",
252252
mcp.Required(),
@@ -265,10 +265,11 @@ func RunWorkflow(getClient GetClientFn, t translations.TranslationHelperFunc) (t
265265
iferr!=nil {
266266
returnmcp.NewToolResultError(err.Error()),nil
267267
}
268-
workflowFile,err:=RequiredParam[string](request,"workflow_file")
268+
workflowIDInt,err:=RequiredInt(request,"workflow_id")
269269
iferr!=nil {
270270
returnmcp.NewToolResultError(err.Error()),nil
271271
}
272+
workflowID:=int64(workflowIDInt)
272273
ref,err:=RequiredParam[string](request,"ref")
273274
iferr!=nil {
274275
returnmcp.NewToolResultError(err.Error()),nil
@@ -292,15 +293,17 @@ func RunWorkflow(getClient GetClientFn, t translations.TranslationHelperFunc) (t
292293
Inputs:inputs,
293294
}
294295

295-
resp,err:=client.Actions.CreateWorkflowDispatchEventByFileName(ctx,owner,repo,workflowFile,event)
296+
// Convert workflow ID to string format for the API call
297+
workflowIDStr:=fmt.Sprintf("%d",workflowID)
298+
resp,err:=client.Actions.CreateWorkflowDispatchEventByFileName(ctx,owner,repo,workflowIDStr,event)
296299
iferr!=nil {
297300
returnnil,fmt.Errorf("failed to run workflow: %w",err)
298301
}
299302
deferfunc() {_=resp.Body.Close() }()
300303

301304
result:=map[string]any{
302305
"message":"Workflow run has been queued",
303-
"workflow":workflowFile,
306+
"workflow_id":workflowID,
304307
"ref":ref,
305308
"inputs":inputs,
306309
"status":resp.Status,
@@ -316,6 +319,93 @@ func RunWorkflow(getClient GetClientFn, t translations.TranslationHelperFunc) (t
316319
}
317320
}
318321

322+
// RunWorkflowByFileName creates a tool to run an Actions workflow by filename
323+
funcRunWorkflowByFileName(getClientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
324+
returnmcp.NewTool("run_workflow_by_filename",
325+
mcp.WithDescription(t("TOOL_RUN_WORKFLOW_BY_FILENAME_DESCRIPTION","Run an Actions workflow by workflow filename")),
326+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
327+
ReadOnlyHint:ToBoolPtr(false),
328+
}),
329+
mcp.WithString("owner",
330+
mcp.Required(),
331+
mcp.Description("Repository owner"),
332+
),
333+
mcp.WithString("repo",
334+
mcp.Required(),
335+
mcp.Description("Repository name"),
336+
),
337+
mcp.WithString("workflow_file",
338+
mcp.Required(),
339+
mcp.Description("The workflow file name (e.g., main.yml, ci.yaml)"),
340+
),
341+
mcp.WithString("ref",
342+
mcp.Required(),
343+
mcp.Description("The git reference for the workflow. The reference can be a branch or tag name."),
344+
),
345+
mcp.WithObject("inputs",
346+
mcp.Description("Inputs the workflow accepts"),
347+
),
348+
),
349+
func(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
350+
owner,err:=RequiredParam[string](request,"owner")
351+
iferr!=nil {
352+
returnmcp.NewToolResultError(err.Error()),nil
353+
}
354+
repo,err:=RequiredParam[string](request,"repo")
355+
iferr!=nil {
356+
returnmcp.NewToolResultError(err.Error()),nil
357+
}
358+
workflowFile,err:=RequiredParam[string](request,"workflow_file")
359+
iferr!=nil {
360+
returnmcp.NewToolResultError(err.Error()),nil
361+
}
362+
ref,err:=RequiredParam[string](request,"ref")
363+
iferr!=nil {
364+
returnmcp.NewToolResultError(err.Error()),nil
365+
}
366+
367+
// Get optional inputs parameter
368+
varinputsmap[string]interface{}
369+
ifrequestInputs,ok:=request.GetArguments()["inputs"];ok {
370+
ifinputsMap,ok:=requestInputs.(map[string]interface{});ok {
371+
inputs=inputsMap
372+
}
373+
}
374+
375+
client,err:=getClient(ctx)
376+
iferr!=nil {
377+
returnnil,fmt.Errorf("failed to get GitHub client: %w",err)
378+
}
379+
380+
event:= github.CreateWorkflowDispatchEventRequest{
381+
Ref:ref,
382+
Inputs:inputs,
383+
}
384+
385+
resp,err:=client.Actions.CreateWorkflowDispatchEventByFileName(ctx,owner,repo,workflowFile,event)
386+
iferr!=nil {
387+
returnnil,fmt.Errorf("failed to run workflow: %w",err)
388+
}
389+
deferfunc() {_=resp.Body.Close() }()
390+
391+
result:=map[string]any{
392+
"message":"Workflow run has been queued",
393+
"workflow_file":workflowFile,
394+
"ref":ref,
395+
"inputs":inputs,
396+
"status":resp.Status,
397+
"status_code":resp.StatusCode,
398+
}
399+
400+
r,err:=json.Marshal(result)
401+
iferr!=nil {
402+
returnnil,fmt.Errorf("failed to marshal response: %w",err)
403+
}
404+
405+
returnmcp.NewToolResultText(string(r)),nil
406+
}
407+
}
408+
319409
// GetWorkflowRun creates a tool to get details of a specific workflow run
320410
funcGetWorkflowRun(getClientGetClientFn,t translations.TranslationHelperFunc) (tool mcp.Tool,handler server.ToolHandlerFunc) {
321411
returnmcp.NewTool("get_workflow_run",

‎pkg/github/actions_test.go

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,90 @@ func Test_RunWorkflow(t *testing.T) {
134134
assert.NotEmpty(t,tool.Description)
135135
assert.Contains(t,tool.InputSchema.Properties,"owner")
136136
assert.Contains(t,tool.InputSchema.Properties,"repo")
137+
assert.Contains(t,tool.InputSchema.Properties,"workflow_id")
138+
assert.Contains(t,tool.InputSchema.Properties,"ref")
139+
assert.Contains(t,tool.InputSchema.Properties,"inputs")
140+
assert.ElementsMatch(t,tool.InputSchema.Required, []string{"owner","repo","workflow_id","ref"})
141+
142+
tests:= []struct {
143+
namestring
144+
mockedClient*http.Client
145+
requestArgsmap[string]any
146+
expectErrorbool
147+
expectedErrMsgstring
148+
}{
149+
{
150+
name:"successful workflow run",
151+
mockedClient:mock.NewMockedHTTPClient(
152+
mock.WithRequestMatchHandler(
153+
mock.PostReposActionsWorkflowsDispatchesByOwnerByRepoByWorkflowId,
154+
http.HandlerFunc(func(w http.ResponseWriter,_*http.Request) {
155+
w.WriteHeader(http.StatusNoContent)
156+
}),
157+
),
158+
),
159+
requestArgs:map[string]any{
160+
"owner":"owner",
161+
"repo":"repo",
162+
"workflow_id":float64(12345),
163+
"ref":"main",
164+
},
165+
expectError:false,
166+
},
167+
{
168+
name:"missing required parameter workflow_id",
169+
mockedClient:mock.NewMockedHTTPClient(),
170+
requestArgs:map[string]any{
171+
"owner":"owner",
172+
"repo":"repo",
173+
"ref":"main",
174+
},
175+
expectError:true,
176+
expectedErrMsg:"missing required parameter: workflow_id",
177+
},
178+
}
179+
180+
for_,tc:=rangetests {
181+
t.Run(tc.name,func(t*testing.T) {
182+
// Setup client with mock
183+
client:=github.NewClient(tc.mockedClient)
184+
_,handler:=RunWorkflow(stubGetClientFn(client),translations.NullTranslationHelper)
185+
186+
// Create call request
187+
request:=createMCPRequest(tc.requestArgs)
188+
189+
// Call handler
190+
result,err:=handler(context.Background(),request)
191+
192+
require.NoError(t,err)
193+
require.Equal(t,tc.expectError,result.IsError)
194+
195+
// Parse the result and get the text content if no error
196+
textContent:=getTextResult(t,result)
197+
198+
iftc.expectedErrMsg!="" {
199+
assert.Equal(t,tc.expectedErrMsg,textContent.Text)
200+
return
201+
}
202+
203+
// Unmarshal and verify the result
204+
varresponsemap[string]any
205+
err=json.Unmarshal([]byte(textContent.Text),&response)
206+
require.NoError(t,err)
207+
assert.Equal(t,"Workflow run has been queued",response["message"])
208+
})
209+
}
210+
}
211+
212+
funcTest_RunWorkflowByFileName(t*testing.T) {
213+
// Verify tool definition once
214+
mockClient:=github.NewClient(nil)
215+
tool,_:=RunWorkflowByFileName(stubGetClientFn(mockClient),translations.NullTranslationHelper)
216+
217+
assert.Equal(t,"run_workflow_by_filename",tool.Name)
218+
assert.NotEmpty(t,tool.Description)
219+
assert.Contains(t,tool.InputSchema.Properties,"owner")
220+
assert.Contains(t,tool.InputSchema.Properties,"repo")
137221
assert.Contains(t,tool.InputSchema.Properties,"workflow_file")
138222
assert.Contains(t,tool.InputSchema.Properties,"ref")
139223
assert.Contains(t,tool.InputSchema.Properties,"inputs")
@@ -147,7 +231,7 @@ func Test_RunWorkflow(t *testing.T) {
147231
expectedErrMsgstring
148232
}{
149233
{
150-
name:"successful workflow run",
234+
name:"successful workflow run by filename",
151235
mockedClient:mock.NewMockedHTTPClient(
152236
mock.WithRequestMatchHandler(
153237
mock.PostReposActionsWorkflowsDispatchesByOwnerByRepoByWorkflowId,
@@ -181,7 +265,7 @@ func Test_RunWorkflow(t *testing.T) {
181265
t.Run(tc.name,func(t*testing.T) {
182266
// Setup client with mock
183267
client:=github.NewClient(tc.mockedClient)
184-
_,handler:=RunWorkflow(stubGetClientFn(client),translations.NullTranslationHelper)
268+
_,handler:=RunWorkflowByFileName(stubGetClientFn(client),translations.NullTranslationHelper)
185269

186270
// Create call request
187271
request:=createMCPRequest(tc.requestArgs)

‎pkg/github/tools.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
125125
).
126126
AddWriteTools(
127127
toolsets.NewServerTool(RunWorkflow(getClient,t)),
128+
toolsets.NewServerTool(RunWorkflowByFileName(getClient,t)),
128129
toolsets.NewServerTool(RerunWorkflowRun(getClient,t)),
129130
toolsets.NewServerTool(RerunFailedJobs(getClient,t)),
130131
toolsets.NewServerTool(CancelWorkflowRun(getClient,t)),

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp