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

Commitdf1ca76

Browse files
committed
add more tools!
1 parentba898b9 commitdf1ca76

File tree

3 files changed

+274
-4
lines changed

3 files changed

+274
-4
lines changed

‎mcp/mcp.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,12 @@ func New(ctx context.Context, client *codersdk.Client, opts ...Option) io.Closer
7676

7777
mcptools.RegisterCoderReportTask(mcpSrv,client,logger)
7878
mcptools.RegisterCoderWhoami(mcpSrv,client)
79+
mcptools.RegisterCoderListTemplates(mcpSrv,client)
7980
mcptools.RegisterCoderListWorkspaces(mcpSrv,client)
81+
mcptools.RegisterCoderGetWorkspace(mcpSrv,client)
8082
mcptools.RegisterCoderWorkspaceExec(mcpSrv,client)
83+
mcptools.RegisterCoderStartWorkspace(mcpSrv,client)
84+
mcptools.RegisterCoderStopWorkspace(mcpSrv,client)
8185

8286
srv:=server.NewStdioServer(mcpSrv)
8387
srv.SetErrorLogger(log.New(options.out,"",log.LstdFlags))

‎mcp/tools/tools_coder.go

Lines changed: 184 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,26 @@ func RegisterCoderListWorkspaces(ta ToolAdder, client *codersdk.Client) {
3535
ta.AddTool(toolCoderListWorkspaces,handleCoderListWorkspaces(client))
3636
}
3737

38+
funcRegisterCoderGetWorkspace(taToolAdder,client*codersdk.Client) {
39+
ta.AddTool(toolCoderGetWorkspace,handleCoderGetWorkspace(client))
40+
}
41+
3842
funcRegisterCoderWorkspaceExec(taToolAdder,client*codersdk.Client) {
3943
ta.AddTool(toolCoderWorkspaceExec,handleCoderWorkspaceExec(client))
4044
}
4145

46+
funcRegisterCoderListTemplates(taToolAdder,client*codersdk.Client) {
47+
ta.AddTool(toolCoderListTemplates,handleCoderListTemplates(client))
48+
}
49+
50+
funcRegisterCoderStartWorkspace(taToolAdder,client*codersdk.Client) {
51+
ta.AddTool(toolCoderStartWorkspace,handleCoderStartWorkspace(client))
52+
}
53+
54+
funcRegisterCoderStopWorkspace(taToolAdder,client*codersdk.Client) {
55+
ta.AddTool(toolCoderStopWorkspace,handleCoderStopWorkspace(client))
56+
}
57+
4258
var (
4359
toolCoderReportTask=mcp.NewTool("coder_report_task",
4460
mcp.WithDescription(`Report progress on a task.`),
@@ -61,16 +77,31 @@ Good Summaries:
6177
mcp.WithNumber(`offset`,mcp.Description(`The offset to start listing workspaces from. Defaults to 0.`),mcp.DefaultNumber(0)),
6278
mcp.WithNumber(`limit`,mcp.Description(`The maximum number of workspaces to list. Defaults to 10.`),mcp.DefaultNumber(10)),
6379
)
80+
toolCoderGetWorkspace=mcp.NewTool("coder_get_workspace",
81+
mcp.WithDescription(`Get information about a workspace on a given Coder deployment.`),
82+
mcp.WithString("workspace",mcp.Description(`The workspace ID or name to get.`),mcp.Required()),
83+
)
6484
toolCoderWorkspaceExec=mcp.NewTool("coder_workspace_exec",
6585
mcp.WithDescription(`Execute a command in a remote workspace on a given Coder deployment.`),
6686
// Required parameters.
6787
mcp.WithString("workspace",mcp.Description(`The workspace ID or name in which to execute the command in. The workspace must be running.`),mcp.Required()),
6888
mcp.WithString("command",mcp.Description(`The command to execute. Changing the working directory is not currently supported, so you may need to preface the command with 'cd /some/path && <my-command>'.`),mcp.Required()),
6989
)
90+
toolCoderListTemplates=mcp.NewTool("coder_list_templates",
91+
mcp.WithDescription(`List all templates on a given Coder deployment.`),
92+
)
93+
toolCoderStartWorkspace=mcp.NewTool("coder_start_workspace",
94+
mcp.WithDescription(`Start a workspace on a given Coder deployment.`),
95+
mcp.WithString("workspace",mcp.Description(`The workspace ID or name to start.`),mcp.Required()),
96+
)
97+
toolCoderStopWorkspace=mcp.NewTool("coder_stop_workspace",
98+
mcp.WithDescription(`Stop a workspace on a given Coder deployment.`),
99+
mcp.WithString("workspace",mcp.Description(`The workspace ID or name to stop.`),mcp.Required()),
100+
)
70101
)
71102

72103
// Example payload:
73-
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_report_task", "arguments": {"summary": "I'm working on the login page.", "link": "https://github.com/coder/coder/pull/1234", "emoji": "🔍", "done": false, "coder_url": "http://localhost:3000", "coder_session_token": "REDACTED"}}}
104+
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_report_task", "arguments": {"summary": "I'm working on the login page.", "link": "https://github.com/coder/coder/pull/1234", "emoji": "🔍", "done": false}}}
74105
funchandleCoderReportTask(log slog.Logger,client*codersdk.Client) mcpserver.ToolHandlerFunc {
75106
returnfunc(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
76107
ifclient==nil {
@@ -123,7 +154,7 @@ func handleCoderReportTask(log slog.Logger, client *codersdk.Client) mcpserver.T
123154
}
124155

125156
// Example payload:
126-
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_whoami", "arguments": {"coder_url": "http://localhost:3000", "coder_session_token": "REDACTED"}}}
157+
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_whoami", "arguments": {}}}
127158
funchandleCoderWhoami(client*codersdk.Client) mcpserver.ToolHandlerFunc {
128159
returnfunc(ctx context.Context,_ mcp.CallToolRequest) (*mcp.CallToolResult,error) {
129160
ifclient==nil {
@@ -148,7 +179,7 @@ func handleCoderWhoami(client *codersdk.Client) mcpserver.ToolHandlerFunc {
148179
}
149180

150181
// Example payload:
151-
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_list_workspaces", "arguments": {"owner": "me", "offset": 0, "limit": 10, "coder_url": "http://localhost:3000", "coder_session_token": "REDACTED"}}}
182+
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_list_workspaces", "arguments": {"owner": "me", "offset": 0, "limit": 10}}}
152183
funchandleCoderListWorkspaces(client*codersdk.Client) mcpserver.ToolHandlerFunc {
153184
returnfunc(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
154185
ifclient==nil {
@@ -194,7 +225,39 @@ func handleCoderListWorkspaces(client *codersdk.Client) mcpserver.ToolHandlerFun
194225
}
195226

196227
// Example payload:
197-
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_workspace_exec", "arguments": {"workspace": "dev", "command": "ps -ef", "coder_url": "http://localhost:3000", "coder_session_token": "REDACTED"}}}
228+
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_get_workspace", "arguments": {"workspace": "dev"}}}
229+
funchandleCoderGetWorkspace(client*codersdk.Client) mcpserver.ToolHandlerFunc {
230+
returnfunc(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
231+
ifclient==nil {
232+
returnnil,xerrors.New("developer error: client is required")
233+
}
234+
args:=request.Params.Arguments
235+
236+
wsArg,ok:=args["workspace"].(string)
237+
if!ok {
238+
returnnil,xerrors.New("workspace is required")
239+
}
240+
241+
workspace,err:=getWorkspaceByIDOrOwnerName(ctx,client,wsArg)
242+
iferr!=nil {
243+
returnnil,xerrors.Errorf("failed to fetch workspace: %w",err)
244+
}
245+
246+
workspaceJSON,err:=json.Marshal(workspace)
247+
iferr!=nil {
248+
returnnil,xerrors.Errorf("failed to encode workspace: %w",err)
249+
}
250+
251+
return&mcp.CallToolResult{
252+
Content: []mcp.Content{
253+
mcp.NewTextContent(string(workspaceJSON)),
254+
},
255+
},nil
256+
}
257+
}
258+
259+
// Example payload:
260+
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_workspace_exec", "arguments": {"workspace": "dev", "command": "ps -ef"}}}
198261
funchandleCoderWorkspaceExec(client*codersdk.Client) mcpserver.ToolHandlerFunc {
199262
returnfunc(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
200263
ifclient==nil {
@@ -265,6 +328,123 @@ func handleCoderWorkspaceExec(client *codersdk.Client) mcpserver.ToolHandlerFunc
265328
}
266329
}
267330

331+
// Example payload:
332+
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_list_templates", "arguments": {}}}
333+
funchandleCoderListTemplates(client*codersdk.Client) mcpserver.ToolHandlerFunc {
334+
returnfunc(ctx context.Context,_ mcp.CallToolRequest) (*mcp.CallToolResult,error) {
335+
ifclient==nil {
336+
returnnil,xerrors.New("developer error: client is required")
337+
}
338+
templates,err:=client.Templates(ctx, codersdk.TemplateFilter{})
339+
iferr!=nil {
340+
returnnil,xerrors.Errorf("failed to fetch templates: %w",err)
341+
}
342+
343+
templateJSON,err:=json.Marshal(templates)
344+
iferr!=nil {
345+
returnnil,xerrors.Errorf("failed to encode templates: %w",err)
346+
}
347+
348+
return&mcp.CallToolResult{
349+
Content: []mcp.Content{
350+
mcp.NewTextContent(string(templateJSON)),
351+
},
352+
},nil
353+
}
354+
}
355+
356+
// Example payload:
357+
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_start_workspace", "arguments": {"workspace": "dev"}}}
358+
funchandleCoderStartWorkspace(client*codersdk.Client) mcpserver.ToolHandlerFunc {
359+
returnfunc(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
360+
ifclient==nil {
361+
returnnil,xerrors.New("developer error: client is required")
362+
}
363+
364+
args:=request.Params.Arguments
365+
366+
wsArg,ok:=args["workspace"].(string)
367+
if!ok {
368+
returnnil,xerrors.New("workspace is required")
369+
}
370+
371+
workspace,err:=getWorkspaceByIDOrOwnerName(ctx,client,wsArg)
372+
iferr!=nil {
373+
returnnil,xerrors.Errorf("failed to fetch workspace: %w",err)
374+
}
375+
376+
switchworkspace.LatestBuild.Status {
377+
casecodersdk.WorkspaceStatusPending,codersdk.WorkspaceStatusStarting,codersdk.WorkspaceStatusRunning,codersdk.WorkspaceStatusCanceling:
378+
returnnil,xerrors.Errorf("workspace is %s",workspace.LatestBuild.Status)
379+
}
380+
381+
wb,err:=client.CreateWorkspaceBuild(ctx,workspace.ID, codersdk.CreateWorkspaceBuildRequest{
382+
Transition:codersdk.WorkspaceTransitionStart,
383+
})
384+
iferr!=nil {
385+
returnnil,xerrors.Errorf("failed to start workspace: %w",err)
386+
}
387+
388+
resp:=map[string]any{"status":wb.Status,"transition":wb.Transition}
389+
respJSON,err:=json.Marshal(resp)
390+
iferr!=nil {
391+
returnnil,xerrors.Errorf("failed to encode workspace build: %w",err)
392+
}
393+
394+
return&mcp.CallToolResult{
395+
Content: []mcp.Content{
396+
mcp.NewTextContent(string(respJSON)),
397+
},
398+
},nil
399+
}
400+
}
401+
402+
// Example payload:
403+
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_stop_workspace", "arguments": {"workspace": "dev"}}}
404+
funchandleCoderStopWorkspace(client*codersdk.Client) mcpserver.ToolHandlerFunc {
405+
returnfunc(ctx context.Context,request mcp.CallToolRequest) (*mcp.CallToolResult,error) {
406+
ifclient==nil {
407+
returnnil,xerrors.New("developer error: client is required")
408+
}
409+
410+
args:=request.Params.Arguments
411+
412+
wsArg,ok:=args["workspace"].(string)
413+
if!ok {
414+
returnnil,xerrors.New("workspace is required")
415+
}
416+
417+
workspace,err:=getWorkspaceByIDOrOwnerName(ctx,client,wsArg)
418+
iferr!=nil {
419+
returnnil,xerrors.Errorf("failed to fetch workspace: %w",err)
420+
}
421+
422+
switchworkspace.LatestBuild.Status {
423+
casecodersdk.WorkspaceStatusPending,codersdk.WorkspaceStatusStopping,codersdk.WorkspaceStatusStopped,codersdk.WorkspaceStatusCanceling:
424+
returnnil,xerrors.Errorf("workspace is %s",workspace.LatestBuild.Status)
425+
}
426+
427+
wb,err:=client.CreateWorkspaceBuild(ctx,workspace.ID, codersdk.CreateWorkspaceBuildRequest{
428+
Transition:codersdk.WorkspaceTransitionStop,
429+
})
430+
iferr!=nil {
431+
returnnil,xerrors.Errorf("failed to stop workspace: %w",err)
432+
}
433+
434+
resp:=map[string]any{"status":wb.Status,"transition":wb.Transition}
435+
respJSON,err:=json.Marshal(resp)
436+
iferr!=nil {
437+
returnnil,xerrors.Errorf("failed to encode workspace build: %w",err)
438+
}
439+
440+
return&mcp.CallToolResult{
441+
Content: []mcp.Content{
442+
mcp.NewTextContent(string(respJSON)),
443+
},
444+
},nil
445+
}
446+
}
447+
268448
funcgetWorkspaceByIDOrOwnerName(ctx context.Context,client*codersdk.Client,identifierstring) (codersdk.Workspace,error) {
269449
ifwsid,err:=uuid.Parse(identifier);err==nil {
270450
returnclient.Workspace(ctx,wsid)

‎mcp/tools/tools_coder_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,29 @@ func TestCoderTools(t *testing.T) {
5757
mcptools.RegisterCoderReportTask(mcpSrv,memberClient,slogtest.Make(t,nil))
5858
mcptools.RegisterCoderWhoami(mcpSrv,memberClient)
5959
mcptools.RegisterCoderListWorkspaces(mcpSrv,memberClient)
60+
mcptools.RegisterCoderGetWorkspace(mcpSrv,memberClient)
6061
mcptools.RegisterCoderWorkspaceExec(mcpSrv,memberClient)
62+
mcptools.RegisterCoderListTemplates(mcpSrv,memberClient)
63+
mcptools.RegisterCoderStartWorkspace(mcpSrv,memberClient)
64+
mcptools.RegisterCoderStopWorkspace(mcpSrv,memberClient)
65+
66+
t.Run("coder_list_templates",func(t*testing.T) {
67+
// When: the coder_list_templates tool is called
68+
ctr:=makeJSONRPCRequest(t,"tools/call","coder_list_templates",map[string]any{})
69+
70+
pty.WriteLine(ctr)
71+
_=pty.ReadLine(ctx)// skip the echo
72+
73+
templates,err:=memberClient.Templates(ctx, codersdk.TemplateFilter{})
74+
require.NoError(t,err)
75+
templatesJSON,err:=json.Marshal(templates)
76+
require.NoError(t,err)
77+
78+
// Then: the response is a list of templates visible to the user.
79+
expected:=makeJSONRPCTextResponse(t,string(templatesJSON))
80+
actual:=pty.ReadLine(ctx)
81+
testutil.RequireJSONEq(t,expected,actual)
82+
})
6183

6284
t.Run("coder_report_task",func(t*testing.T) {
6385
// When: the coder_report_task tool is called
@@ -119,6 +141,26 @@ func TestCoderTools(t *testing.T) {
119141
testutil.RequireJSONEq(t,expected,actual)
120142
})
121143

144+
t.Run("coder_get_workspace",func(t*testing.T) {
145+
// When: the coder_get_workspace tool is called
146+
ctr:=makeJSONRPCRequest(t,"tools/call","coder_get_workspace",map[string]any{
147+
"workspace":r.Workspace.ID.String(),
148+
})
149+
150+
pty.WriteLine(ctr)
151+
_=pty.ReadLine(ctx)// skip the echo
152+
153+
ws,err:=memberClient.Workspace(ctx,r.Workspace.ID)
154+
require.NoError(t,err)
155+
wsJSON,err:=json.Marshal(ws)
156+
require.NoError(t,err)
157+
158+
// Then: the response is a valid JSON respresentation of the workspace.
159+
expected:=makeJSONRPCTextResponse(t,string(wsJSON))
160+
actual:=pty.ReadLine(ctx)
161+
testutil.RequireJSONEq(t,expected,actual)
162+
})
163+
122164
t.Run("coder_workspace_exec",func(t*testing.T) {
123165
// When: the coder_workspace_exec tools is called with a command
124166
randString:=testutil.GetRandomName(t)
@@ -137,6 +179,50 @@ func TestCoderTools(t *testing.T) {
137179
actual:=pty.ReadLine(ctx)
138180
testutil.RequireJSONEq(t,expected,actual)
139181
})
182+
183+
t.Run("coder_stop_workspace",func(t*testing.T) {
184+
// Given: a separate workspace in the running state
185+
stopWs:=dbfake.WorkspaceBuild(t,store, database.WorkspaceTable{
186+
OrganizationID:owner.OrganizationID,
187+
OwnerID:member.ID,
188+
}).WithAgent().Do()
189+
190+
// When: the coder_stop_workspace tool is called
191+
ctr:=makeJSONRPCRequest(t,"tools/call","coder_stop_workspace",map[string]any{
192+
"workspace":stopWs.Workspace.ID.String(),
193+
})
194+
195+
pty.WriteLine(ctr)
196+
_=pty.ReadLine(ctx)// skip the echo
197+
198+
// Then: the response is as expected.
199+
expected:=makeJSONRPCTextResponse(t,`{"status":"pending","transition":"stop"}`)// no provisionerd yet
200+
actual:=pty.ReadLine(ctx)
201+
testutil.RequireJSONEq(t,expected,actual)
202+
})
203+
204+
t.Run("coder_start_workspace",func(t*testing.T) {
205+
// Given: a separate workspace in the stopped state
206+
stopWs:=dbfake.WorkspaceBuild(t,store, database.WorkspaceTable{
207+
OrganizationID:owner.OrganizationID,
208+
OwnerID:member.ID,
209+
}).Seed(database.WorkspaceBuild{
210+
Transition:database.WorkspaceTransitionStop,
211+
}).Do()
212+
213+
// When: the coder_start_workspace tool is called
214+
ctr:=makeJSONRPCRequest(t,"tools/call","coder_start_workspace",map[string]any{
215+
"workspace":stopWs.Workspace.ID.String(),
216+
})
217+
218+
pty.WriteLine(ctr)
219+
_=pty.ReadLine(ctx)// skip the echo
220+
221+
// Then: the response is as expected
222+
expected:=makeJSONRPCTextResponse(t,`{"status":"pending","transition":"start"}`)// no provisionerd yet
223+
actual:=pty.ReadLine(ctx)
224+
testutil.RequireJSONEq(t,expected,actual)
225+
})
140226
}
141227

142228
// makeJSONRPCRequest is a helper function that makes a JSON RPC request.

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp