Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitd46ea56

Browse files
committed
Use common flags for agent client in MCP server
Before we were separately reading the environment, but we can reuse theglobal flags and the function for creating an agent client.Also, since the tests no longer need to set environment variables theycan now be ran in parallel.This is not fully backwards compatible in that the old method would fallback to the non-agent client URL if the agent URL was not set, but thatseems like undesired behavior since we are not doing that anywhere else.
1 parent4678def commitd46ea56

File tree

2 files changed

+146
-147
lines changed

2 files changed

+146
-147
lines changed

‎cli/exp_mcp.go‎

Lines changed: 92 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"context"
66
"encoding/json"
77
"errors"
8-
"net/url"
98
"os"
109
"path/filepath"
1110
"slices"
@@ -24,6 +23,10 @@ import (
2423
"github.com/coder/serpent"
2524
)
2625

26+
const (
27+
envAppStatusSlug="CODER_MCP_APP_STATUS_SLUG"
28+
)
29+
2730
func (r*RootCmd)mcpCommand()*serpent.Command {
2831
cmd:=&serpent.Command{
2932
Use:"mcp",
@@ -110,7 +113,7 @@ func (*RootCmd) mcpConfigureClaudeDesktop() *serpent.Command {
110113
returncmd
111114
}
112115

113-
func (*RootCmd)mcpConfigureClaudeCode()*serpent.Command {
116+
func (r*RootCmd)mcpConfigureClaudeCode()*serpent.Command {
114117
var (
115118
claudeAPIKeystring
116119
claudeConfigPathstring
@@ -139,11 +142,12 @@ func (*RootCmd) mcpConfigureClaudeCode() *serpent.Command {
139142
binPath=testBinaryName
140143
}
141144
configureClaudeEnv:=map[string]string{}
142-
agentToken,err:=getAgentToken(fs)
145+
agentClient,err:=r.createAgentClient()
143146
iferr!=nil {
144-
cliui.Warnf(inv.Stderr,"failed toget agenttoken: %s",err)
147+
cliui.Warnf(inv.Stderr,"failed tocreate agentclient: %s",err)
145148
}else {
146-
configureClaudeEnv["CODER_AGENT_TOKEN"]=agentToken
149+
configureClaudeEnv[envAgentURL]=agentClient.SDK.URL.String()
150+
configureClaudeEnv[envAgentToken]=agentClient.SDK.SessionToken()
147151
}
148152
ifclaudeAPIKey=="" {
149153
ifdeprecatedCoderMCPClaudeAPIKey=="" {
@@ -154,7 +158,7 @@ func (*RootCmd) mcpConfigureClaudeCode() *serpent.Command {
154158
}
155159
}
156160
ifappStatusSlug!="" {
157-
configureClaudeEnv["CODER_MCP_APP_STATUS_SLUG"]=appStatusSlug
161+
configureClaudeEnv[envAppStatusSlug]=appStatusSlug
158162
}
159163
ifdeprecatedSystemPromptEnv,ok:=os.LookupEnv("SYSTEM_PROMPT");ok {
160164
cliui.Warnf(inv.Stderr,"SYSTEM_PROMPT is deprecated, use CODER_MCP_CLAUDE_SYSTEM_PROMPT instead")
@@ -181,7 +185,7 @@ func (*RootCmd) mcpConfigureClaudeCode() *serpent.Command {
181185

182186
// Determine if we should include the reportTaskPrompt
183187
varreportTaskPromptstring
184-
ifagentToken!=""&&appStatusSlug!="" {
188+
ifagentClient!=nil&&appStatusSlug!="" {
185189
// Only include the report task prompt if both agent token and app
186190
// status slug are defined. Otherwise, reporting a task will fail
187191
// and confuse the agent (and by extension, the user).
@@ -250,7 +254,7 @@ func (*RootCmd) mcpConfigureClaudeCode() *serpent.Command {
250254
{
251255
Name:"app-status-slug",
252256
Description:"The app status slug to use when running the Coder MCP server.",
253-
Env:"CODER_MCP_APP_STATUS_SLUG",
257+
Env:envAppStatusSlug,
254258
Flag:"claude-app-status-slug",
255259
Value:serpent.StringOf(&appStatusSlug),
256260
},
@@ -343,6 +347,12 @@ func (*RootCmd) mcpConfigureCursor() *serpent.Command {
343347
returncmd
344348
}
345349

350+
typemcpServerstruct {
351+
agentClient*agentsdk.Client
352+
appStatusSlugstring
353+
client*codersdk.Client
354+
}
355+
346356
func (r*RootCmd)mcpServer()*serpent.Command {
347357
var (
348358
client=new(codersdk.Client)
@@ -353,7 +363,53 @@ func (r *RootCmd) mcpServer() *serpent.Command {
353363
return&serpent.Command{
354364
Use:"server",
355365
Handler:func(inv*serpent.Invocation)error {
356-
returnmcpServerHandler(inv,client,instructions,allowedTools,appStatusSlug)
366+
srv:=&mcpServer{
367+
appStatusSlug:appStatusSlug,
368+
}
369+
370+
// Display client URL separately from authentication status.
371+
ifclient!=nil&&client.URL!=nil {
372+
cliui.Infof(inv.Stderr,"URL : %s",client.URL.String())
373+
}else {
374+
cliui.Infof(inv.Stderr,"URL : Not configured")
375+
}
376+
377+
// Validate the client.
378+
ifclient!=nil&&client.URL!=nil&&client.SessionToken()!="" {
379+
me,err:=client.User(inv.Context(),codersdk.Me)
380+
iferr==nil {
381+
username:=me.Username
382+
cliui.Infof(inv.Stderr,"Authentication : Successful")
383+
cliui.Infof(inv.Stderr,"User : %s",username)
384+
srv.client=client
385+
}else {
386+
cliui.Infof(inv.Stderr,"Authentication : Failed (%s)",err)
387+
cliui.Warnf(inv.Stderr,"Some tools that require authentication will not be available.")
388+
}
389+
}else {
390+
cliui.Infof(inv.Stderr,"Authentication : None")
391+
}
392+
393+
// Try to create an agent client for status reporting. Not validated.
394+
agentClient,err:=r.createAgentClient()
395+
iferr==nil {
396+
cliui.Infof(inv.Stderr,"Agent URL : %s",agentClient.SDK.URL.String())
397+
srv.agentClient=agentClient
398+
}
399+
iferr!=nil||appStatusSlug=="" {
400+
cliui.Infof(inv.Stderr,"Task reporter : Disabled")
401+
iferr!=nil {
402+
cliui.Warnf(inv.Stderr,"%s",err)
403+
}
404+
ifappStatusSlug=="" {
405+
cliui.Warnf(inv.Stderr,"%s must be set",envAppStatusSlug)
406+
}
407+
}else {
408+
cliui.Infof(inv.Stderr,"Task reporter : Enabled")
409+
}
410+
411+
// Start the server.
412+
returnsrv.start(inv,instructions,allowedTools)
357413
},
358414
Short:"Start the Coder MCP server.",
359415
Middleware:serpent.Chain(
@@ -378,54 +434,24 @@ func (r *RootCmd) mcpServer() *serpent.Command {
378434
Name:"app-status-slug",
379435
Description:"When reporting a task, the coder_app slug under which to report the task.",
380436
Flag:"app-status-slug",
381-
Env:"CODER_MCP_APP_STATUS_SLUG",
437+
Env:envAppStatusSlug,
382438
Value:serpent.StringOf(&appStatusSlug),
383439
Default:"",
384440
},
385441
},
386442
}
387443
}
388444

389-
funcmcpServerHandler(inv*serpent.Invocation,client*codersdk.Client,instructionsstring,allowedTools []string,appStatusSlugstring)error {
445+
func(s*mcpServer)start(inv*serpent.Invocation,instructionsstring,allowedTools []string)error {
390446
ctx,cancel:=context.WithCancel(inv.Context())
391447
defercancel()
392448

393-
fs:=afero.NewOsFs()
394-
395449
cliui.Infof(inv.Stderr,"Starting MCP server")
396450

397-
// Check authentication status
398-
varusernamestring
399-
400-
// Check authentication status first
401-
ifclient!=nil&&client.URL!=nil&&client.SessionToken()!="" {
402-
// Try to validate the client
403-
me,err:=client.User(ctx,codersdk.Me)
404-
iferr==nil {
405-
username=me.Username
406-
cliui.Infof(inv.Stderr,"Authentication : Successful")
407-
cliui.Infof(inv.Stderr,"User : %s",username)
408-
}else {
409-
// Authentication failed but we have a client URL
410-
cliui.Warnf(inv.Stderr,"Authentication : Failed (%s)",err)
411-
cliui.Warnf(inv.Stderr,"Some tools that require authentication will not be available.")
412-
}
413-
}else {
414-
cliui.Infof(inv.Stderr,"Authentication : None")
415-
}
416-
417-
// Display URL separately from authentication status
418-
ifclient!=nil&&client.URL!=nil {
419-
cliui.Infof(inv.Stderr,"URL : %s",client.URL.String())
420-
}else {
421-
cliui.Infof(inv.Stderr,"URL : Not configured")
422-
}
423-
424451
cliui.Infof(inv.Stderr,"Instructions : %q",instructions)
425452
iflen(allowedTools)>0 {
426453
cliui.Infof(inv.Stderr,"Allowed Tools : %v",allowedTools)
427454
}
428-
cliui.Infof(inv.Stderr,"Press Ctrl+C to stop the server")
429455

430456
// Capture the original stdin, stdout, and stderr.
431457
invStdin:=inv.Stdin
@@ -443,68 +469,44 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct
443469
server.WithInstructions(instructions),
444470
)
445471

446-
// Get the workspace agent token from the environment.
447-
toolOpts:=make([]func(*toolsdk.Deps),0)
448-
varhasAgentClientbool
449-
450-
varagentURL*url.URL
451-
ifclient!=nil&&client.URL!=nil {
452-
agentURL=client.URL
453-
}elseifagntURL,err:=getAgentURL();err==nil {
454-
agentURL=agntURL
455-
}
456-
457-
// First check if we have a valid client URL, which is required for agent client
458-
ifagentURL==nil {
459-
cliui.Infof(inv.Stderr,"Agent URL : Not configured")
460-
}else {
461-
cliui.Infof(inv.Stderr,"Agent URL : %s",agentURL.String())
462-
agentToken,err:=getAgentToken(fs)
463-
iferr!=nil||agentToken=="" {
464-
cliui.Warnf(inv.Stderr,"CODER_AGENT_TOKEN is not set, task reporting will not be available")
465-
}else {
466-
// Happy path: we have both URL and agent token
467-
agentClient:=agentsdk.New(agentURL)
468-
agentClient.SetSessionToken(agentToken)
469-
toolOpts=append(toolOpts,toolsdk.WithAgentClient(agentClient))
470-
hasAgentClient=true
471-
}
472-
}
473-
474-
if (client==nil||client.URL==nil||client.SessionToken()=="")&&!hasAgentClient {
472+
// If both clients are unauthorized, there are no tools we can enable.
473+
ifs.client==nil&&s.agentClient==nil {
475474
returnxerrors.New(notLoggedInMessage)
476475
}
477476

478-
ifappStatusSlug!="" {
479-
toolOpts=append(toolOpts,toolsdk.WithAppStatusSlug(appStatusSlug))
480-
}else {
481-
cliui.Warnf(inv.Stderr,"CODER_MCP_APP_STATUS_SLUG is not set, task reporting will not be available.")
477+
// Add tool dependencies.
478+
toolOpts:= []func(*toolsdk.Deps){
479+
toolsdk.WithAgentClient(s.agentClient),
480+
toolsdk.WithAppStatusSlug(s.appStatusSlug),
482481
}
483482

484-
toolDeps,err:=toolsdk.NewDeps(client,toolOpts...)
483+
toolDeps,err:=toolsdk.NewDeps(s.client,toolOpts...)
485484
iferr!=nil {
486485
returnxerrors.Errorf("failed to initialize tool dependencies: %w",err)
487486
}
488487

489-
// Register tools based on the allowlist (if specified)
488+
// Register tools based on the allowlist. Zero length means allow everything.
490489
for_,tool:=rangetoolsdk.All {
491-
// Skip adding the coder_report_task tool if there is no agent client
492-
if!hasAgentClient&&tool.Tool.Name=="coder_report_task" {
493-
cliui.Warnf(inv.Stderr,"Task reporting not available")
490+
// Skip if not allowed.
491+
iflen(allowedTools)>0&&!slices.ContainsFunc(allowedTools,func(tstring)bool {
492+
returnt==tool.Tool.Name
493+
}) {
494494
continue
495495
}
496496

497-
// Skip user-dependent tools if no authenticated user
498-
if!tool.UserClientOptional&&username=="" {
497+
// Skip user-dependent tools if no authenticated user client.
498+
if!tool.UserClientOptional&&s.client==nil {
499499
cliui.Warnf(inv.Stderr,"Tool %q requires authentication and will not be available",tool.Tool.Name)
500500
continue
501501
}
502502

503-
iflen(allowedTools)==0||slices.ContainsFunc(allowedTools,func(tstring)bool {
504-
returnt==tool.Tool.Name
505-
}) {
506-
mcpSrv.AddTools(mcpFromSDK(tool,toolDeps))
503+
// Skip the coder_report_task tool if there is no agent client or slug.
504+
iftool.Tool.Name=="coder_report_task"&& (s.agentClient==nil||s.appStatusSlug=="") {
505+
cliui.Warnf(inv.Stderr,"Tool %q requires the task reporter and will not be available",tool.Tool.Name)
506+
continue
507507
}
508+
509+
mcpSrv.AddTools(mcpFromSDK(tool,toolDeps))
508510
}
509511

510512
srv:=server.NewStdioServer(mcpSrv)
@@ -515,11 +517,11 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct
515517
done<-srvErr
516518
}()
517519

518-
iferr:=<-done;err!=nil {
519-
if!errors.Is(err,context.Canceled) {
520-
cliui.Errorf(inv.Stderr,"Failed to start the MCP server: %s",err)
521-
returnerr
522-
}
520+
cliui.Infof(inv.Stderr,"Press Ctrl+C to stop the server")
521+
522+
iferr:=<-done;err!=nil&&!errors.Is(err,context.Canceled) {
523+
cliui.Errorf(inv.Stderr,"Failed to start the MCP server: %s",err)
524+
returnerr
523525
}
524526

525527
returnnil
@@ -738,31 +740,6 @@ func indexOf(s, substr string) int {
738740
return-1
739741
}
740742

741-
funcgetAgentToken(fs afero.Fs) (string,error) {
742-
token,ok:=os.LookupEnv("CODER_AGENT_TOKEN")
743-
ifok&&token!="" {
744-
returntoken,nil
745-
}
746-
tokenFile,ok:=os.LookupEnv("CODER_AGENT_TOKEN_FILE")
747-
if!ok {
748-
return"",xerrors.Errorf("CODER_AGENT_TOKEN or CODER_AGENT_TOKEN_FILE must be set for token auth")
749-
}
750-
bs,err:=afero.ReadFile(fs,tokenFile)
751-
iferr!=nil {
752-
return"",xerrors.Errorf("failed to read agent token file: %w",err)
753-
}
754-
returnstring(bs),nil
755-
}
756-
757-
funcgetAgentURL() (*url.URL,error) {
758-
urlString,ok:=os.LookupEnv("CODER_AGENT_URL")
759-
if!ok||urlString=="" {
760-
returnnil,xerrors.New("CODEDR_AGENT_URL is empty")
761-
}
762-
763-
returnurl.Parse(urlString)
764-
}
765-
766743
// mcpFromSDK adapts a toolsdk.Tool to go-mcp's server.ServerTool.
767744
// It assumes that the tool responds with a valid JSON object.
768745
funcmcpFromSDK(sdkTool toolsdk.GenericTool,tb toolsdk.Deps) server.ServerTool {

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp