sipeed/picoclaw · error

failed to reach MCP server %q: %w

Error message

failed to reach MCP server %q: %w

What it means

Wrapper around the probe failure in picoclaw mcp test <name>. The probe forces Enabled=true on a single-server Manager config and calls LoadFromMCPConfig, which spawns the local command or dials the remote URL and performs the MCP handshake. Any failure — spawn errors, path validation, transport errors, handshake or timeout — is reported under this message with %w, preserving the underlying cause. The probe runs under --timeout, default 5s.

Source

Thrown at cmd/picoclaw/internal/mcp/test.go:35

		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			cfg, err := loadConfig()
			if err != nil {
				return err
			}

			name := args[0]
			server, exists := cfg.Tools.MCP.Servers[name]
			if !exists {
				return fmt.Errorf("MCP server %q not found", name)
			}

			ctx, cancel := context.WithTimeout(context.Background(), timeout)
			defer cancel()

			result, err := serverProbe(ctx, name, server, cfg.WorkspacePath())
			if err != nil {
				return fmt.Errorf("failed to reach MCP server %q: %w", name, err)
			}

			fmt.Fprintf(cmd.OutOrStdout(), "✓ MCP server %q reachable (%d tools).\n", name, result.ToolCount)
			return nil
		},
	}

	cmd.Flags().DurationVar(&timeout, "timeout", 5*time.Second, "Connection timeout")

	return cmd
}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Raise the budget: picoclaw mcp test <name> --timeout 30s
  2. Run the configured command manually with the same env/args and check it starts and speaks MCP on stdio
  3. Verify command path (exists, is a file, is executable) and env/env_file contents
  4. Read the wrapped error — it distinguishes spawn, transport, and handshake failures

Example fix

# before
$ picoclaw mcp test weather
failed to reach MCP server "weather": failed to connect to server weather: context deadline exceeded

# after
$ picoclaw mcp test weather --timeout 30s
Defensive patterns

Strategy: retry

Validate before calling

// pre-flight the stdio command before testing connectivity
if cmd := server.Command; cmd != "" {
  if _, err := exec.LookPath(cmd); err != nil {
    if info, statErr := os.Stat(cmd); statErr != nil || info.IsDir() {
      return fmt.Errorf("command %s unusable: %v", cmd, err)
    }
  }
}

Type guard

func isReachFailure(err error) bool {
  return err != nil && strings.Contains(err.Error(), "failed to reach MCP server")
}

func isProbeTimeout(err error) bool {
  return errors.Is(err, context.DeadlineExceeded)
}

Try / catch

var result probeResult
var err error
for attempt := 0; attempt < 3; attempt++ {
  ctx, cancel := context.WithTimeout(context.Background(), timeout*time.Duration(attempt+1))
  result, err = serverProbe(ctx, name, server, ws)
  cancel()
  if err == nil || !errors.Is(err, context.DeadlineExceeded) {
    break // only retry timeouts; hard failures surface immediately
  }
}

Prevention

When it happens

Trigger: Server process fails to start (missing binary, missing env secrets, bad args); local command is a directory or lacks the execute bit; remote endpoint refused/unreachable; handshake exceeding the 5s default on slow-starting servers (e.g. first run of npx/uvx downloading packages).

Common situations: Local servers whose secrets live only in the user's shell; cold starts of package-run servers exceeding 5s; wrong command path after moving a binary; firewalled or mis-routed remote MCP URLs.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/20aa91400ad0adb7. Report an issue: GitHub.