feat(cli): make MCP server work without user authentication (#17688)

Part of #17649

---

# Allow MCP server to run without authentication

This PR enhances the MCP server to operate without requiring authentication, making it more flexible for environments where authentication isn't available or necessary. Key changes:

- Replaced `InitClient` with `TryInitClient` to allow the MCP server to start without credentials
- Added graceful handling when URL or authentication is missing
- Made authentication status visible in server logs
- Added logic to skip user-dependent tools when no authenticated user is present
- Made the `coder_report_task` tool available with just an agent token (no user token required)
- Added comprehensive tests to verify operation without authentication

These changes allow the MCP server to function in more environments while still using authentication when available, improving flexibility for CI/CD and other automated environments.
This commit is contained in:
Thomas Kosiewski
2025-05-07 21:53:06 +02:00
committed by GitHub
parent 6ac1bd807c
commit 29bce8d9e6
5 changed files with 253 additions and 23 deletions

View File

@ -22,9 +22,8 @@ func NewDeps(client *codersdk.Client, opts ...func(*Deps)) (Deps, error) {
for _, opt := range opts {
opt(&d)
}
if d.coderClient == nil {
return Deps{}, xerrors.New("developer error: coder client may not be nil")
}
// Allow nil client for unauthenticated operation
// This enables tools that don't require user authentication to function
return d, nil
}
@ -54,6 +53,11 @@ type HandlerFunc[Arg, Ret any] func(context.Context, Deps, Arg) (Ret, error)
type Tool[Arg, Ret any] struct {
aisdk.Tool
Handler HandlerFunc[Arg, Ret]
// UserClientOptional indicates whether this tool can function without a valid
// user authentication token. If true, the tool will be available even when
// running in an unauthenticated mode with just an agent token.
UserClientOptional bool
}
// Generic returns a type-erased version of a TypedTool where the arguments and
@ -63,7 +67,8 @@ type Tool[Arg, Ret any] struct {
// conversion.
func (t Tool[Arg, Ret]) Generic() GenericTool {
return GenericTool{
Tool: t.Tool,
Tool: t.Tool,
UserClientOptional: t.UserClientOptional,
Handler: wrap(func(ctx context.Context, deps Deps, args json.RawMessage) (json.RawMessage, error) {
var typedArgs Arg
if err := json.Unmarshal(args, &typedArgs); err != nil {
@ -85,6 +90,11 @@ func (t Tool[Arg, Ret]) Generic() GenericTool {
type GenericTool struct {
aisdk.Tool
Handler GenericHandlerFunc
// UserClientOptional indicates whether this tool can function without a valid
// user authentication token. If true, the tool will be available even when
// running in an unauthenticated mode with just an agent token.
UserClientOptional bool
}
// GenericHandlerFunc is a function that handles a tool call.
@ -195,6 +205,7 @@ var ReportTask = Tool[ReportTaskArgs, codersdk.Response]{
Required: []string{"summary", "link", "state"},
},
},
UserClientOptional: true,
Handler: func(ctx context.Context, deps Deps, args ReportTaskArgs) (codersdk.Response, error) {
if deps.agentClient == nil {
return codersdk.Response{}, xerrors.New("tool unavailable as CODER_AGENT_TOKEN or CODER_AGENT_TOKEN_FILE not set")