6
6
"encoding/json"
7
7
"errors"
8
8
"io"
9
- "os"
10
9
"slices"
11
10
"strings"
12
11
"time"
@@ -17,76 +16,12 @@ import (
17
16
"golang.org/x/xerrors"
18
17
19
18
"cdr.dev/slog"
20
- "cdr.dev/slog/sloggers/sloghuman"
21
- "github.com/coder/coder/v2/buildinfo"
22
19
"github.com/coder/coder/v2/coderd/util/ptr"
23
20
"github.com/coder/coder/v2/codersdk"
21
+ "github.com/coder/coder/v2/codersdk/agentsdk"
24
22
"github.com/coder/coder/v2/codersdk/workspacesdk"
25
23
)
26
24
27
- type mcpOptions struct {
28
- instructions string
29
- logger * slog.Logger
30
- allowedTools []string
31
- }
32
-
33
- // Option is a function that configures the MCP server.
34
- type Option func (* mcpOptions )
35
-
36
- // WithInstructions sets the instructions for the MCP server.
37
- func WithInstructions (instructions string ) Option {
38
- return func (o * mcpOptions ) {
39
- o .instructions = instructions
40
- }
41
- }
42
-
43
- // WithLogger sets the logger for the MCP server.
44
- func WithLogger (logger * slog.Logger ) Option {
45
- return func (o * mcpOptions ) {
46
- o .logger = logger
47
- }
48
- }
49
-
50
- // WithAllowedTools sets the allowed tools for the MCP server.
51
- func WithAllowedTools (tools []string ) Option {
52
- return func (o * mcpOptions ) {
53
- o .allowedTools = tools
54
- }
55
- }
56
-
57
- // NewStdio creates a new MCP stdio server with the given client and options.
58
- // It is the responsibility of the caller to start and stop the server.
59
- func NewStdio (client * codersdk.Client , opts ... Option ) * server.StdioServer {
60
- options := & mcpOptions {
61
- instructions : `` ,
62
- logger : ptr .Ref (slog .Make (sloghuman .Sink (os .Stdout ))),
63
- }
64
- for _ , opt := range opts {
65
- opt (options )
66
- }
67
-
68
- mcpSrv := server .NewMCPServer (
69
- "Coder Agent" ,
70
- buildinfo .Version (),
71
- server .WithInstructions (options .instructions ),
72
- )
73
-
74
- logger := slog .Make (sloghuman .Sink (os .Stdout ))
75
-
76
- // Register tools based on the allowed list (if specified)
77
- reg := AllTools ()
78
- if len (options .allowedTools ) > 0 {
79
- reg = reg .WithOnlyAllowed (options .allowedTools ... )
80
- }
81
- reg .Register (mcpSrv , ToolDeps {
82
- Client : client ,
83
- Logger : & logger ,
84
- })
85
-
86
- srv := server .NewStdioServer (mcpSrv )
87
- return srv
88
- }
89
-
90
25
// allTools is the list of all available tools. When adding a new tool,
91
26
// make sure to update this list.
92
27
var allTools = ToolRegistry {
@@ -120,6 +55,8 @@ Choose an emoji that helps the user understand the current phase at a glance.`),
120
55
mcp .WithBoolean ("done" , mcp .Description (`Whether the overall task the user requested is complete.
121
56
Set to true only when the entire requested operation is finished successfully.
122
57
For multi-step processes, use false until all steps are complete.` ), mcp .Required ()),
58
+ mcp .WithBoolean ("need_user_attention" , mcp .Description (`Whether the user needs to take action on the task.
59
+ Set to true if the task is in a failed state or if the user needs to take action to continue.` ), mcp .Required ()),
123
60
),
124
61
MakeHandler : handleCoderReportTask ,
125
62
},
@@ -265,8 +202,10 @@ Can be either "start" or "stop".`)),
265
202
266
203
// ToolDeps contains all dependencies needed by tool handlers
267
204
type ToolDeps struct {
268
- Client * codersdk.Client
269
- Logger * slog.Logger
205
+ Client * codersdk.Client
206
+ AgentClient * agentsdk.Client
207
+ Logger * slog.Logger
208
+ AppStatusSlug string
270
209
}
271
210
272
211
// ToolHandler associates a tool with its handler creation function
@@ -313,18 +252,23 @@ func AllTools() ToolRegistry {
313
252
}
314
253
315
254
type handleCoderReportTaskArgs struct {
316
- Summary string `json:"summary"`
317
- Link string `json:"link"`
318
- Emoji string `json:"emoji"`
319
- Done bool `json:"done"`
255
+ Summary string `json:"summary"`
256
+ Link string `json:"link"`
257
+ Emoji string `json:"emoji"`
258
+ Done bool `json:"done"`
259
+ NeedUserAttention bool `json:"need_user_attention"`
320
260
}
321
261
322
262
// Example payload:
323
- // {"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}}}
263
+ // {"jsonrpc":"2.0","id":1,"method":"tools/call", "params": {"name": "coder_report_task", "arguments": {"summary": "I need help with the login page.", "link": "https://github.com/coder/coder/pull/1234", "emoji": "🔍", "done": false, "need_user_attention": true }}}
324
264
func handleCoderReportTask (deps ToolDeps ) server.ToolHandlerFunc {
325
265
return func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
326
- if deps .Client == nil {
327
- return nil , xerrors .New ("developer error: client is required" )
266
+ if deps .AgentClient == nil {
267
+ return nil , xerrors .New ("developer error: agent client is required" )
268
+ }
269
+
270
+ if deps .AppStatusSlug == "" {
271
+ return nil , xerrors .New ("No app status slug provided, set CODER_MCP_APP_STATUS_SLUG when running the MCP server to report tasks." )
328
272
}
329
273
330
274
// Convert the request parameters to a json.RawMessage so we can unmarshal
@@ -334,20 +278,33 @@ func handleCoderReportTask(deps ToolDeps) server.ToolHandlerFunc {
334
278
return nil , xerrors .Errorf ("failed to unmarshal arguments: %w" , err )
335
279
}
336
280
337
- // TODO: Waiting on support for tasks.
338
- 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 ))
339
- /*
340
- err := sdk.PostTask(ctx, agentsdk.PostTaskRequest{
341
- Reporter: "claude",
342
- Summary: summary,
343
- URL: link,
344
- Completion: done,
345
- Icon: emoji,
346
- })
347
- if err != nil {
348
- return nil, err
349
- }
350
- */
281
+ deps .Logger .Info (ctx , "report task tool called" ,
282
+ slog .F ("summary" , args .Summary ),
283
+ slog .F ("link" , args .Link ),
284
+ slog .F ("emoji" , args .Emoji ),
285
+ slog .F ("done" , args .Done ),
286
+ slog .F ("need_user_attention" , args .NeedUserAttention ),
287
+ )
288
+
289
+ newStatus := agentsdk.PatchAppStatus {
290
+ AppSlug : deps .AppStatusSlug ,
291
+ Message : args .Summary ,
292
+ URI : args .Link ,
293
+ Icon : args .Emoji ,
294
+ NeedsUserAttention : args .NeedUserAttention ,
295
+ State : codersdk .WorkspaceAppStatusStateWorking ,
296
+ }
297
+
298
+ if args .Done {
299
+ newStatus .State = codersdk .WorkspaceAppStatusStateComplete
300
+ }
301
+ if args .NeedUserAttention {
302
+ newStatus .State = codersdk .WorkspaceAppStatusStateFailure
303
+ }
304
+
305
+ if err := deps .AgentClient .PatchAppStatus (ctx , newStatus ); err != nil {
306
+ return nil , xerrors .Errorf ("failed to patch app status: %w" , err )
307
+ }
351
308
352
309
return & mcp.CallToolResult {
353
310
Content : []mcp.Content {
0 commit comments