@@ -20,6 +20,13 @@ import (
20
20
"github.com/coder/coder/v2/codersdk/workspacesdk"
21
21
)
22
22
23
+ type handleCoderReportTaskArgs struct {
24
+ Summary string `json:"summary"`
25
+ Link string `json:"link"`
26
+ Emoji string `json:"emoji"`
27
+ Done bool `json:"done"`
28
+ }
29
+
23
30
// Example payload:
24
31
// {"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}}}
25
32
func handleCoderReportTask (deps ToolDeps ) mcpserver.ToolHandlerFunc {
@@ -28,30 +35,15 @@ func handleCoderReportTask(deps ToolDeps) mcpserver.ToolHandlerFunc {
28
35
return nil , xerrors .New ("developer error: client is required" )
29
36
}
30
37
31
- args := request .Params .Arguments
32
-
33
- summary , ok := args ["summary" ].(string )
34
- if ! ok {
35
- return nil , xerrors .New ("summary is required" )
36
- }
37
-
38
- link , ok := args ["link" ].(string )
39
- if ! ok {
40
- return nil , xerrors .New ("link is required" )
41
- }
42
-
43
- emoji , ok := args ["emoji" ].(string )
44
- if ! ok {
45
- return nil , xerrors .New ("emoji is required" )
46
- }
47
-
48
- done , ok := args ["done" ].(bool )
49
- if ! ok {
50
- return nil , xerrors .New ("done is required" )
38
+ // Convert the request parameters to a json.RawMessage so we can unmarshal
39
+ // them into the correct struct.
40
+ args , err := unmarshalArgs [handleCoderReportTaskArgs ](request .Params .Arguments )
41
+ if err != nil {
42
+ return nil , xerrors .Errorf ("failed to unmarshal arguments: %w" , err )
51
43
}
52
44
53
45
// TODO: Waiting on support for tasks.
54
- deps .Logger .Info (ctx , "report task tool called" , slog .F ("summary" , summary ), slog .F ("link" , link ), slog .F ("done" , done ), slog .F ("emoji" , emoji ))
46
+ deps .Logger .Info (ctx , "report task tool called" , slog .F ("summary" , args . Summary ), slog .F ("link" , args . Link ), slog .F ("done" , args . Done ), slog .F ("emoji" , args . Emoji ))
55
47
/*
56
48
err := sdk.PostTask(ctx, agentsdk.PostTaskRequest{
57
49
Reporter: "claude",
@@ -98,33 +90,28 @@ func handleCoderWhoami(deps ToolDeps) mcpserver.ToolHandlerFunc {
98
90
}
99
91
}
100
92
93
+ type handleCoderListWorkspacesArgs struct {
94
+ Owner string `json:"owner"`
95
+ Offset int `json:"offset"`
96
+ Limit int `json:"limit"`
97
+ }
98
+
101
99
// Example payload:
102
100
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_list_workspaces", "arguments": {"owner": "me", "offset": 0, "limit": 10}}}
103
101
func handleCoderListWorkspaces (deps ToolDeps ) mcpserver.ToolHandlerFunc {
104
102
return func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
105
103
if deps .Client == nil {
106
104
return nil , xerrors .New ("developer error: client is required" )
107
105
}
108
- args := request .Params .Arguments
109
-
110
- owner , ok := args ["owner" ].(string )
111
- if ! ok {
112
- owner = codersdk .Me
113
- }
114
-
115
- offset , ok := args ["offset" ].(int )
116
- if ! ok || offset < 0 {
117
- offset = 0
118
- }
119
- limit , ok := args ["limit" ].(int )
120
- if ! ok || limit <= 0 {
121
- limit = 10
106
+ args , err := unmarshalArgs [handleCoderListWorkspacesArgs ](request .Params .Arguments )
107
+ if err != nil {
108
+ return nil , xerrors .Errorf ("failed to unmarshal arguments: %w" , err )
122
109
}
123
110
124
111
workspaces , err := deps .Client .Workspaces (ctx , codersdk.WorkspaceFilter {
125
- Owner : owner ,
126
- Offset : offset ,
127
- Limit : limit ,
112
+ Owner : args . Owner ,
113
+ Offset : args . Offset ,
114
+ Limit : args . Limit ,
128
115
})
129
116
if err != nil {
130
117
return nil , xerrors .Errorf ("failed to fetch workspaces: %w" , err )
@@ -144,21 +131,23 @@ func handleCoderListWorkspaces(deps ToolDeps) mcpserver.ToolHandlerFunc {
144
131
}
145
132
}
146
133
134
+ type handleCoderGetWorkspaceArgs struct {
135
+ Workspace string `json:"workspace"`
136
+ }
137
+
147
138
// Example payload:
148
139
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_get_workspace", "arguments": {"workspace": "dev"}}}
149
140
func handleCoderGetWorkspace (deps ToolDeps ) mcpserver.ToolHandlerFunc {
150
141
return func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
151
142
if deps .Client == nil {
152
143
return nil , xerrors .New ("developer error: client is required" )
153
144
}
154
- args := request .Params .Arguments
155
-
156
- wsArg , ok := args ["workspace" ].(string )
157
- if ! ok {
158
- return nil , xerrors .New ("workspace is required" )
145
+ args , err := unmarshalArgs [handleCoderGetWorkspaceArgs ](request .Params .Arguments )
146
+ if err != nil {
147
+ return nil , xerrors .Errorf ("failed to unmarshal arguments: %w" , err )
159
148
}
160
149
161
- workspace , err := getWorkspaceByIDOrOwnerName (ctx , deps .Client , wsArg )
150
+ workspace , err := getWorkspaceByIDOrOwnerName (ctx , deps .Client , args . Workspace )
162
151
if err != nil {
163
152
return nil , xerrors .Errorf ("failed to fetch workspace: %w" , err )
164
153
}
@@ -176,28 +165,26 @@ func handleCoderGetWorkspace(deps ToolDeps) mcpserver.ToolHandlerFunc {
176
165
}
177
166
}
178
167
168
+ type handleCoderWorkspaceExecArgs struct {
169
+ Workspace string `json:"workspace"`
170
+ Command string `json:"command"`
171
+ }
172
+
179
173
// Example payload:
180
174
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_workspace_exec", "arguments": {"workspace": "dev", "command": "ps -ef"}}}
181
175
func handleCoderWorkspaceExec (deps ToolDeps ) mcpserver.ToolHandlerFunc {
182
176
return func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
183
177
if deps .Client == nil {
184
178
return nil , xerrors .New ("developer error: client is required" )
185
179
}
186
- args := request .Params .Arguments
187
-
188
- wsArg , ok := args ["workspace" ].(string )
189
- if ! ok {
190
- return nil , xerrors .New ("workspace is required" )
191
- }
192
-
193
- command , ok := args ["command" ].(string )
194
- if ! ok {
195
- return nil , xerrors .New ("command is required" )
180
+ args , err := unmarshalArgs [handleCoderWorkspaceExecArgs ](request .Params .Arguments )
181
+ if err != nil {
182
+ return nil , xerrors .Errorf ("failed to unmarshal arguments: %w" , err )
196
183
}
197
184
198
185
// Attempt to fetch the workspace. We may get a UUID or a name, so try to
199
186
// handle both.
200
- ws , err := getWorkspaceByIDOrOwnerName (ctx , deps .Client , wsArg )
187
+ ws , err := getWorkspaceByIDOrOwnerName (ctx , deps .Client , args . Workspace )
201
188
if err != nil {
202
189
return nil , xerrors .Errorf ("failed to fetch workspace: %w" , err )
203
190
}
@@ -224,7 +211,7 @@ func handleCoderWorkspaceExec(deps ToolDeps) mcpserver.ToolHandlerFunc {
224
211
Reconnect : uuid .New (),
225
212
Width : 80 ,
226
213
Height : 24 ,
227
- Command : command ,
214
+ Command : args . Command ,
228
215
BackendType : "buffered" , // the screen backend is annoying to use here.
229
216
})
230
217
if err != nil {
@@ -288,6 +275,11 @@ func handleCoderListTemplates(deps ToolDeps) mcpserver.ToolHandlerFunc {
288
275
}
289
276
}
290
277
278
+ type handleCoderWorkspaceTransitionArgs struct {
279
+ Workspace string `json:"workspace"`
280
+ Transition string `json:"transition"`
281
+ }
282
+
291
283
// Example payload:
292
284
// {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name":
293
285
// "coder_workspace_transition", "arguments": {"workspace": "dev", "transition": "stop"}}}
@@ -296,24 +288,17 @@ func handleCoderWorkspaceTransition(deps ToolDeps) mcpserver.ToolHandlerFunc {
296
288
if deps .Client == nil {
297
289
return nil , xerrors .New ("developer error: client is required" )
298
290
}
299
-
300
- args := request .Params .Arguments
301
-
302
- wsArg , ok := args ["workspace" ].(string )
303
- if ! ok {
304
- return nil , xerrors .New ("workspace is required" )
291
+ args , err := unmarshalArgs [handleCoderWorkspaceTransitionArgs ](request .Params .Arguments )
292
+ if err != nil {
293
+ return nil , xerrors .Errorf ("failed to unmarshal arguments: %w" , err )
305
294
}
306
295
307
- workspace , err := getWorkspaceByIDOrOwnerName (ctx , deps .Client , wsArg )
296
+ workspace , err := getWorkspaceByIDOrOwnerName (ctx , deps .Client , args . Workspace )
308
297
if err != nil {
309
298
return nil , xerrors .Errorf ("failed to fetch workspace: %w" , err )
310
299
}
311
300
312
- transition , ok := args ["transition" ].(string )
313
- if ! ok {
314
- return nil , xerrors .New ("transition is required" )
315
- }
316
- wsTransition := codersdk .WorkspaceTransition (transition )
301
+ wsTransition := codersdk .WorkspaceTransition (args .Transition )
317
302
switch wsTransition {
318
303
case codersdk .WorkspaceTransitionStart :
319
304
case codersdk .WorkspaceTransitionStop :
@@ -350,3 +335,17 @@ func getWorkspaceByIDOrOwnerName(ctx context.Context, client *codersdk.Client, i
350
335
}
351
336
return client .WorkspaceByOwnerAndName (ctx , codersdk .Me , identifier , codersdk.WorkspaceOptions {})
352
337
}
338
+
339
+ // unmarshalArgs is a helper function to convert the map[string]any we get from
340
+ // the MCP server into a typed struct. It does this by marshaling and unmarshalling
341
+ // the arguments.
342
+ func unmarshalArgs [T any ](args map [string ]interface {}) (t T , err error ) {
343
+ argsJSON , err := json .Marshal (args )
344
+ if err != nil {
345
+ return t , xerrors .Errorf ("failed to marshal arguments: %w" , err )
346
+ }
347
+ if err := json .Unmarshal (argsJSON , & t ); err != nil {
348
+ return t , xerrors .Errorf ("failed to unmarshal arguments: %w" , err )
349
+ }
350
+ return t , nil
351
+ }
0 commit comments