66 "encoding/json"
77 "errors"
88 "io"
9- "os"
109 "slices"
1110 "strings"
1211 "time"
@@ -17,76 +16,12 @@ import (
1716 "golang.org/x/xerrors"
1817
1918 "cdr.dev/slog"
20- "cdr.dev/slog/sloggers/sloghuman"
21- "github.com/coder/coder/v2/buildinfo"
2219 "github.com/coder/coder/v2/coderd/util/ptr"
2320 "github.com/coder/coder/v2/codersdk"
21+ "github.com/coder/coder/v2/codersdk/agentsdk"
2422 "github.com/coder/coder/v2/codersdk/workspacesdk"
2523)
2624
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-
9025// allTools is the list of all available tools. When adding a new tool,
9126// make sure to update this list.
9227var allTools = ToolRegistry {
@@ -120,6 +55,8 @@ Choose an emoji that helps the user understand the current phase at a glance.`),
12055 mcp .WithBoolean ("done" , mcp .Description (`Whether the overall task the user requested is complete.
12156Set to true only when the entire requested operation is finished successfully.
12257For 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 ()),
12360 ),
12461 MakeHandler : handleCoderReportTask ,
12562 },
@@ -265,8 +202,10 @@ Can be either "start" or "stop".`)),
265202
266203// ToolDeps contains all dependencies needed by tool handlers
267204type 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
270209}
271210
272211// ToolHandler associates a tool with its handler creation function
@@ -313,18 +252,23 @@ func AllTools() ToolRegistry {
313252}
314253
315254type 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"`
320260}
321261
322262// 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 }}}
324264func handleCoderReportTask (deps ToolDeps ) server.ToolHandlerFunc {
325265 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." )
328272 }
329273
330274 // Convert the request parameters to a json.RawMessage so we can unmarshal
@@ -334,20 +278,33 @@ func handleCoderReportTask(deps ToolDeps) server.ToolHandlerFunc {
334278 return nil , xerrors .Errorf ("failed to unmarshal arguments: %w" , err )
335279 }
336280
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+ }
351308
352309 return & mcp.CallToolResult {
353310 Content : []mcp.Content {
0 commit comments