@@ -6,19 +6,19 @@ import (
6
6
"errors"
7
7
"os"
8
8
"path/filepath"
9
+ "slices"
9
10
"strings"
10
11
12
+ "github.com/mark3labs/mcp-go/mcp"
11
13
"github.com/mark3labs/mcp-go/server"
12
14
"github.com/spf13/afero"
13
15
"golang.org/x/xerrors"
14
16
15
- "cdr.dev/slog"
16
- "cdr.dev/slog/sloggers/sloghuman"
17
17
"github.com/coder/coder/v2/buildinfo"
18
18
"github.com/coder/coder/v2/cli/cliui"
19
19
"github.com/coder/coder/v2/codersdk"
20
20
"github.com/coder/coder/v2/codersdk/agentsdk"
21
- codermcp "github.com/coder/coder/v2/mcp "
21
+ "github.com/coder/coder/v2/codersdk/toolsdk "
22
22
"github.com/coder/serpent"
23
23
)
24
24
@@ -365,6 +365,8 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct
365
365
ctx ,cancel := context .WithCancel (inv .Context ())
366
366
defer cancel ()
367
367
368
+ fs := afero .NewOsFs ()
369
+
368
370
me ,err := client .User (ctx ,codersdk .Me )
369
371
if err != nil {
370
372
cliui .Errorf (inv .Stderr ,"Failed to log in to the Coder deployment." )
@@ -397,40 +399,36 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct
397
399
server .WithInstructions (instructions ),
398
400
)
399
401
400
- // Create a separate logger for the tools.
401
- toolLogger := slog .Make (sloghuman .Sink (invStderr ))
402
-
403
- toolDeps := codermcp.ToolDeps {
404
- Client :client ,
405
- Logger :& toolLogger ,
406
- AppStatusSlug :appStatusSlug ,
407
- AgentClient :agentsdk .New (client .URL ),
408
- }
409
-
402
+ // Create a new context for the tools with all relevant information.
403
+ clientCtx := toolsdk .WithClient (ctx ,client )
410
404
// Get the workspace agent token from the environment.
411
- agentToken ,ok := os .LookupEnv ("CODER_AGENT_TOKEN" )
412
- if ok && agentToken != "" {
413
- toolDeps .AgentClient .SetSessionToken (agentToken )
405
+ if agentToken ,err := getAgentToken (fs );err == nil && agentToken != "" {
406
+ agentClient := agentsdk .New (client .URL )
407
+ agentClient .SetSessionToken (agentToken )
408
+ clientCtx = toolsdk .WithAgentClient (clientCtx ,agentClient )
414
409
}else {
415
410
cliui .Warnf (inv .Stderr ,"CODER_AGENT_TOKEN is not set, task reporting will not be available" )
416
411
}
417
- if appStatusSlug = ="" {
412
+ if appStatusSlug ! ="" {
418
413
cliui .Warnf (inv .Stderr ,"CODER_MCP_APP_STATUS_SLUG is not set, task reporting will not be available." )
414
+ }else {
415
+ clientCtx = toolsdk .WithWorkspaceAppStatusSlug (clientCtx ,appStatusSlug )
419
416
}
420
417
421
418
// Register tools based on the allowlist (if specified)
422
- reg := codermcp .AllTools ()
423
- if len (allowedTools )> 0 {
424
- reg = reg .WithOnlyAllowed (allowedTools ... )
419
+ for _ ,tool := range toolsdk .All {
420
+ if len (allowedTools )== 0 || slices .ContainsFunc (allowedTools ,func (t string )bool {
421
+ return t == tool .Tool .Name
422
+ }) {
423
+ mcpSrv .AddTools (mcpFromSDK (tool ))
424
+ }
425
425
}
426
426
427
- reg .Register (mcpSrv ,toolDeps )
428
-
429
427
srv := server .NewStdioServer (mcpSrv )
430
428
done := make (chan error )
431
429
go func () {
432
430
defer close (done )
433
- srvErr := srv .Listen (ctx ,invStdin ,invStdout )
431
+ srvErr := srv .Listen (clientCtx ,invStdin ,invStdout )
434
432
done <- srvErr
435
433
}()
436
434
@@ -674,7 +672,7 @@ func indexOf(s, substr string) int {
674
672
675
673
func getAgentToken (fs afero.Fs ) (string ,error ) {
676
674
token ,ok := os .LookupEnv ("CODER_AGENT_TOKEN" )
677
- if ok {
675
+ if ok && token != "" {
678
676
return token ,nil
679
677
}
680
678
tokenFile ,ok := os .LookupEnv ("CODER_AGENT_TOKEN_FILE" )
@@ -687,3 +685,44 @@ func getAgentToken(fs afero.Fs) (string, error) {
687
685
}
688
686
return string (bs ),nil
689
687
}
688
+
689
+ // mcpFromSDK adapts a toolsdk.Tool to go-mcp's server.ServerTool.
690
+ // It assumes that the tool responds with a valid JSON object.
691
+ func mcpFromSDK (sdkTool toolsdk.Tool [any ]) server.ServerTool {
692
+ return server.ServerTool {
693
+ Tool : mcp.Tool {
694
+ Name :sdkTool .Tool .Name ,
695
+ Description :sdkTool .Description ,
696
+ InputSchema : mcp.ToolInputSchema {
697
+ Type :"object" ,// Default of mcp.NewTool()
698
+ Properties :sdkTool .Schema .Properties ,
699
+ Required :sdkTool .Schema .Required ,
700
+ },
701
+ },
702
+ Handler :func (ctx context.Context ,request mcp.CallToolRequest ) (* mcp.CallToolResult ,error ) {
703
+ result ,err := sdkTool .Handler (ctx ,request .Params .Arguments )
704
+ if err != nil {
705
+ return nil ,err
706
+ }
707
+ var sb strings.Builder
708
+ if err := json .NewEncoder (& sb ).Encode (result );err == nil {
709
+ return & mcp.CallToolResult {
710
+ Content : []mcp.Content {
711
+ mcp .NewTextContent (sb .String ()),
712
+ },
713
+ },nil
714
+ }
715
+ // If the result is not JSON, return it as a string.
716
+ // This is a fallback for tools that return non-JSON data.
717
+ resultStr ,ok := result .(string )
718
+ if ! ok {
719
+ return nil ,xerrors .Errorf ("tool call result is neither valid JSON or a string, got: %T" ,result )
720
+ }
721
+ return & mcp.CallToolResult {
722
+ Content : []mcp.Content {
723
+ mcp .NewTextContent (resultStr ),
724
+ },
725
+ },nil
726
+ },
727
+ }
728
+ }