executeServerCommand runs the specified command, performs the MCP initialization handshake, sends the JSON request to stdin, and returns the response from stdout.
(cmdStr, jsonRequest string)
| 379 | // executeServerCommand runs the specified command, performs the MCP initialization |
| 380 | // handshake, sends the JSON request to stdin, and returns the response from stdout. |
| 381 | func executeServerCommand(cmdStr, jsonRequest string) (string, error) { |
| 382 | // Split the command string into command and arguments |
| 383 | cmdParts := strings.Fields(cmdStr) |
| 384 | if len(cmdParts) == 0 { |
| 385 | return "", fmt.Errorf("empty command") |
| 386 | } |
| 387 | |
| 388 | cmd := exec.Command(cmdParts[0], cmdParts[1:]...) //nolint:gosec //mcpcurl is a test command that needs to execute arbitrary shell commands |
| 389 | |
| 390 | // Setup stdin pipe |
| 391 | stdin, err := cmd.StdinPipe() |
| 392 | if err != nil { |
| 393 | return "", fmt.Errorf("failed to create stdin pipe: %w", err) |
| 394 | } |
| 395 | |
| 396 | // Setup stdout pipe for line-by-line reading |
| 397 | stdoutPipe, err := cmd.StdoutPipe() |
| 398 | if err != nil { |
| 399 | return "", fmt.Errorf("failed to create stdout pipe: %w", err) |
| 400 | } |
| 401 | |
| 402 | // Stderr still uses a buffer |
| 403 | var stderr strings.Builder |
| 404 | cmd.Stderr = &stderr |
| 405 | |
| 406 | // Start the command |
| 407 | if err := cmd.Start(); err != nil { |
| 408 | return "", fmt.Errorf("failed to start command: %w", err) |
| 409 | } |
| 410 | |
| 411 | // Ensure the child process is cleaned up on every return path. |
| 412 | // stdin must be closed before Wait so the server sees EOF and exits; |
| 413 | // its non-zero exit status on EOF is expected, so we ignore the error. |
| 414 | defer func() { |
| 415 | _ = stdin.Close() |
| 416 | _ = cmd.Wait() |
| 417 | }() |
| 418 | |
| 419 | // Use a scanner with a large buffer for reading JSON-RPC responses |
| 420 | scanner := bufio.NewScanner(stdoutPipe) |
| 421 | scanner.Buffer(make([]byte, 0, 1024*1024), 1024*1024) // 1MB max line size |
| 422 | |
| 423 | // Step 1: Send MCP initialize request |
| 424 | initReq, err := buildInitializeRequest() |
| 425 | if err != nil { |
| 426 | return "", fmt.Errorf("failed to build initialize request: %w", err) |
| 427 | } |
| 428 | if _, err := io.WriteString(stdin, initReq+"\n"); err != nil { |
| 429 | return "", fmt.Errorf("failed to write initialize request: %w", err) |
| 430 | } |
| 431 | |
| 432 | // Step 2: Read initialize response (skip any server notifications) |
| 433 | if _, err := readJSONRPCResponse(scanner); err != nil { |
| 434 | return "", fmt.Errorf("failed to read initialize response: %w, stderr: %s", err, stderr.String()) |
| 435 | } |
| 436 | |
| 437 | // Step 3: Send initialized notification |
| 438 | if _, err := io.WriteString(stdin, buildInitializedNotification()+"\n"); err != nil { |
no test coverage detected