diff --git a/cli/exp_mcp.go b/cli/exp_mcp.go index 8b8c96ab41863..35032a43d68fc 100644 --- a/cli/exp_mcp.go +++ b/cli/exp_mcp.go @@ -402,7 +402,9 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct // Create a new context for the tools with all relevant information. clientCtx := toolsdk.WithClient(ctx, client) // Get the workspace agent token from the environment. + var hasAgentClient bool if agentToken, err := getAgentToken(fs); err == nil && agentToken != "" { + hasAgentClient = true agentClient := agentsdk.New(client.URL) agentClient.SetSessionToken(agentToken) clientCtx = toolsdk.WithAgentClient(clientCtx, agentClient) @@ -417,6 +419,11 @@ func mcpServerHandler(inv *serpent.Invocation, client *codersdk.Client, instruct // Register tools based on the allowlist (if specified) for _, tool := range toolsdk.All { + // Skip adding the coder_report_task tool if there is no agent client + if !hasAgentClient && tool.Tool.Name == "coder_report_task" { + cliui.Warnf(inv.Stderr, "Task reporting not available") + continue + } if len(allowedTools) == 0 || slices.ContainsFunc(allowedTools, func(t string) bool { return t == tool.Tool.Name }) { @@ -689,6 +696,11 @@ func getAgentToken(fs afero.Fs) (string, error) { // mcpFromSDK adapts a toolsdk.Tool to go-mcp's server.ServerTool. // It assumes that the tool responds with a valid JSON object. func mcpFromSDK(sdkTool toolsdk.Tool[any]) server.ServerTool { + // NOTE: some clients will silently refuse to use tools if there is an issue + // with the tool's schema or configuration. + if sdkTool.Schema.Properties == nil { + panic("developer error: schema properties cannot be nil") + } return server.ServerTool{ Tool: mcp.Tool{ Name: sdkTool.Tool.Name, diff --git a/codersdk/toolsdk/toolsdk.go b/codersdk/toolsdk/toolsdk.go index 835c37a65180e..134c30c4f1474 100644 --- a/codersdk/toolsdk/toolsdk.go +++ b/codersdk/toolsdk/toolsdk.go @@ -259,6 +259,10 @@ is provisioned correctly and the agent can connect to the control plane. Tool: aisdk.Tool{ Name: "coder_list_templates", Description: "Lists templates for the authenticated user.", + Schema: aisdk.Schema{ + Properties: map[string]any{}, + Required: []string{}, + }, }, Handler: func(ctx context.Context, _ map[string]any) ([]MinimalTemplate, error) { client, err := clientFromContext(ctx) @@ -318,6 +322,10 @@ is provisioned correctly and the agent can connect to the control plane. Tool: aisdk.Tool{ Name: "coder_get_authenticated_user", Description: "Get the currently authenticated user, similar to the `whoami` command.", + Schema: aisdk.Schema{ + Properties: map[string]any{}, + Required: []string{}, + }, }, Handler: func(ctx context.Context, _ map[string]any) (codersdk.User, error) { client, err := clientFromContext(ctx)