siyuan-note/siyuan · error

connect: %w

Error message

connect: %w

What it means

Returned when the MCP handshake (client.Connect over the stdio IOTransport) fails after the child process was successfully started. The child is killed and waited on before returning, so no zombie is left. The wrapped error is the MCP SDK's connect/initialize error.

Source

Thrown at kernel/mcp/client/mcp.go:485

	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, nil, fmt.Errorf("stdout pipe: %w", err)
	}
	cmd.Stderr = io.Discard

	if err := cmd.Start(); err != nil {
		return nil, nil, fmt.Errorf("start command: %w", err)
	}

	connectCtx, connectCancel := context.WithTimeout(ctx, serverTimeout(server))
	defer connectCancel()
	transport := &mcp.IOTransport{Reader: stdout, Writer: stdin}
	session, err := client.Connect(connectCtx, transport, nil)
	if err != nil {
		cmd.Process.Kill()
		cmd.Wait()
		return nil, cmd, fmt.Errorf("connect: %w", err)
	}

	return session, cmd, nil
}

type environmentEntry struct {
	name  string
	value string
}

// buildStdioEnvironment 仅传递用户允许继承的变量,并用显式配置覆盖同名项。
func buildStdioEnvironment(server conf.MCPServer, lookup func(string) (string, bool), resolve func(string) string,
	goos string) ([]string, error) {
	if err := validateMCPServerEnvironment(server, goos); err != nil {
		return nil, err
	}

	entries := map[string]environmentEntry{}

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Run the same command with the same args/env manually in a terminal and confirm it prints a valid MCP initialize response on stdout, not a usage banner or interactive prompt.
  2. Increase server.Timeout (parsed by serverTimeout) if the server legitimately needs longer to start (cold npx/npm/pip installs).
  3. Check the MCP client and server speak a compatible protocol version; update the server or pin a compatible version.
  4. If the server writes diagnostics to stderr, note that cmd.Stderr = io.Discard — temporarily route stderr to a file in a local build to capture the server's startup error.
  5. Because the child is killed on this path, no manual cleanup is needed; just fix the config/restart and retry.

Example fix

// before
server.Command = "my-cli"
server.Args = []string{"serve"}
server.Timeout = 5  // seconds, too short for cold start
// after
server.Command = "my-cli"
server.Args = []string{"serve"}
server.Timeout = 60
Defensive patterns

Strategy: validation

Validate before calling

// Smoke-test that the command speaks MCP before relying on it.
// Run: <command> <args...> and confirm a JSON-RPC initialize response on stdout.
// Pseudocode used in a config validator:
func dryRunInitialize(server conf.MCPServer) error {
    // send {"jsonrpc":"2.0","id":1,"method":"initialize", ...} on stdin
    // expect a JSON-RPC response within server.Timeout
    return nil
}

Try / catch

// After connect failure, child is already killed; surface wrapped SDK error.
// Look for context.DeadlineExceeded to recommend raising server.Timeout.
if errors.Is(err, context.DeadlineExceeded) {
    // suggest increasing server.Timeout
}

Prevention

When it happens

Trigger: connectStdio reaches client.Connect within serverTimeout; the child process starts but does not complete the MCP initialize handshake in time, or speaks a non-MCP protocol on stdout, or crashes immediately, or closes stdout. The code then calls cmd.Process.Kill() and cmd.Wait().

Common situations: The configured command is not actually an MCP server (e.g. a plain CLI that prints a banner then waits); the server needs additional arguments/env it did not get; the server hangs on startup waiting on a TTY or network; serverTimeout is too short for a slow-starting server (e.g. npx downloading a package); protocol-version mismatch between client and server.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/a90f4b985e8837d2. Report an issue: GitHub.