@@ -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
@@ -527,8 +525,8 @@ func configureClaude(fs afero.Fs, cfg ClaudeConfig) error {
527
525
if ! ok {
528
526
mcpServers = make (map [string ]any )
529
527
}
530
- for name , mcp := range cfg .MCPServers {
531
- mcpServers [name ] = mcp
528
+ for name , cfgmcp := range cfg .MCPServers {
529
+ mcpServers [name ] = cfgmcp
532
530
}
533
531
project ["mcpServers" ] = mcpServers
534
532
// Prevents Claude from asking the user to complete the project onboarding.
@@ -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
+ }
0 commit comments