gopls/internal/cmd: absorb tool package harness into cmd

Move the CLI execution harness from gopls/internal/tool into
gopls/internal/cmd, removing package tool entirely. This addresses
review feedback to consolidate command interfaces, profiling flags,
and execution runners into a single cohesive package before
restructuring CLI flag parsing.

And unexport internal structs and fields in package cmd,
root application embeds unexported 'serve serve'. This helps
isolate server instance flags (-listen, -logfile, -debug, -mcp.listen)
so they no longer pollute root 'gopls --help' usage output.

For golang/go#79906

Change-Id: I62689ff6ff7fbf345c64d3c90e14c3510b0729b8
Reviewed-on: https://go-review.googlesource.com/c/tools/+/795680
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/gopls/internal/cmd/call_hierarchy.go b/gopls/internal/cmd/call_hierarchy.go
index 890b21f..906e0ba 100644
--- a/gopls/internal/cmd/call_hierarchy.go
+++ b/gopls/internal/cmd/call_hierarchy.go
@@ -11,12 +11,11 @@
 	"strings"
 
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // callHierarchy implements the callHierarchy verb for gopls.
 type callHierarchy struct {
-	app *Application
+	app *application
 }
 
 func (c *callHierarchy) Name() string      { return "call_hierarchy" }
@@ -36,7 +35,7 @@
 
 func (c *callHierarchy) Run(ctx context.Context, args ...string) error {
 	if len(args) != 1 {
-		return tool.CommandLineErrorf("call_hierarchy expects 1 argument (position)")
+		return commandLineErrorf("call_hierarchy expects 1 argument (position)")
 	}
 
 	cli, _, err := c.app.connect(ctx)
diff --git a/gopls/internal/cmd/capabilities_test.go b/gopls/internal/cmd/capabilities_test.go
index a3a438f..0bac958 100644
--- a/gopls/internal/cmd/capabilities_test.go
+++ b/gopls/internal/cmd/capabilities_test.go
@@ -37,7 +37,7 @@
 	}
 	defer os.RemoveAll(tmpDir)
 
-	app := New()
+	app := newApplication()
 	ctx := context.Background()
 
 	// Initialize the client.
diff --git a/gopls/internal/cmd/check.go b/gopls/internal/cmd/check.go
index a73d1dc..83e7291 100644
--- a/gopls/internal/cmd/check.go
+++ b/gopls/internal/cmd/check.go
@@ -16,7 +16,7 @@
 
 // check implements the check verb for gopls.
 type check struct {
-	app      *Application
+	app      *application
 	Severity string `flag:"severity" help:"minimum diagnostic severity (hint, info, warning, or error)"`
 }
 
diff --git a/gopls/internal/cmd/cmd.go b/gopls/internal/cmd/cmd.go
index fb38663..b0d80d0 100644
--- a/gopls/internal/cmd/cmd.go
+++ b/gopls/internal/cmd/cmd.go
@@ -26,11 +26,10 @@
 	"golang.org/x/tools/gopls/internal/filecache"
 	"golang.org/x/tools/gopls/internal/lsprpc"
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/protocol/command"
+	protocolcommand "golang.org/x/tools/gopls/internal/protocol/command"
 	"golang.org/x/tools/gopls/internal/protocol/semtok"
 	"golang.org/x/tools/gopls/internal/server"
 	"golang.org/x/tools/gopls/internal/settings"
-	"golang.org/x/tools/gopls/internal/tool"
 	"golang.org/x/tools/gopls/internal/util/browser"
 	"golang.org/x/tools/gopls/internal/util/bug"
 	"golang.org/x/tools/gopls/internal/util/moreslices"
@@ -38,18 +37,17 @@
 	"golang.org/x/tools/internal/jsonrpc2"
 )
 
-// Application is the main application as passed to tool.Main
-// It handles the main command line parsing and dispatch to the sub commands.
-type Application struct {
+// application represents the root gopls command and coordinates subcommand dispatch.
+type application struct {
 	// Core application flags
 
 	// Embed the basic profiling flags supported by the tool package
-	tool.Profile
+	ProfileFlags
 
 	// We include the server configuration directly for now, so the flags work
 	// even without the verb.
 	// TODO: Remove this when we stop allowing the serve verb by default.
-	serve Serve
+	serve serve
 
 	// the options configuring function to invoke when building a server
 	options func(*settings.Options)
@@ -121,13 +119,13 @@
 	return args
 }
 
-func (app *Application) verbose() bool {
+func (app *application) verbose() bool {
 	return app.Verbose || app.VeryVerbose
 }
 
-// New returns a new Application ready to run.
-func New() *Application {
-	app := &Application{
+// newApplication returns a new application ready to run.
+func newApplication() *application {
+	app := &application{
 		RemoteFlags: RemoteFlags{
 			RemoteListenTimeout: 1 * time.Minute,
 		},
@@ -136,20 +134,20 @@
 	return app
 }
 
-// Name implements tool.Command returning the binary name.
-func (app *Application) Name() string { return "gopls" }
+// Name implements command returning the binary name.
+func (app *application) Name() string { return "gopls" }
 
-// Usage implements tool.Command returning empty extra argument usage.
-func (app *Application) Usage() string { return "" }
+// Usage implements command returning empty extra argument usage.
+func (app *application) Usage() string { return "" }
 
-// ShortHelp implements tool.Command returning the main binary help.
-func (app *Application) ShortHelp() string {
+// ShortHelp implements command returning the main binary help.
+func (app *application) ShortHelp() string {
 	return ""
 }
 
-// DetailedHelp implements tool.Command returning the main binary help.
+// DetailedHelp implements command returning the main binary help.
 // This includes the short help for all the sub commands.
-func (app *Application) DetailedHelp(f *flag.FlagSet) {
+func (app *application) DetailedHelp(f *flag.FlagSet) {
 	w := tabwriter.NewWriter(f.Output(), 0, 0, 2, ' ', 0)
 	defer w.Flush()
 
@@ -263,7 +261,7 @@
 // sub command as specified by the first argument.
 // If no arguments are passed it will invoke the server sub command, as a
 // temporary measure for compatibility.
-func (app *Application) Run(ctx context.Context, args ...string) error {
+func (app *application) Run(ctx context.Context, args ...string) error {
 	// In the category of "things we can do while waiting for the Go command":
 	// Pre-initialize the filecache, which takes ~50ms to hash the gopls
 	// executable, and immediately runs a gc.
@@ -272,31 +270,31 @@
 	ctx = debug.WithInstance(ctx, app.OTel)
 	if len(args) == 0 {
 		s := flag.NewFlagSet(app.Name(), flag.ExitOnError)
-		return tool.Run(ctx, s, &app.serve, args)
+		return runCommand(ctx, s, &app.serve, args)
 	}
 	command, args := args[0], args[1:]
 	for _, c := range app.Commands() {
 		if c.Name() == command {
 			s := flag.NewFlagSet(app.Name(), flag.ExitOnError)
-			return tool.Run(ctx, s, c, args)
+			return runCommand(ctx, s, c, args)
 		}
 	}
-	return tool.CommandLineErrorf("Unknown command %v", command)
+	return commandLineErrorf("Unknown command %v", command)
 }
 
 // Commands returns the set of commands supported by the gopls tool on the
 // command line.
 // The command is specified by the first non flag argument.
-func (app *Application) Commands() []tool.Command {
-	var commands []tool.Command
+func (app *application) Commands() []command {
+	var commands []command
 	commands = append(commands, app.mainCommands()...)
 	commands = append(commands, app.featureCommands()...)
 	commands = append(commands, app.internalCommands()...)
 	return commands
 }
 
-func (app *Application) mainCommands() []tool.Command {
-	return []tool.Command{
+func (app *application) mainCommands() []command {
+	return []command{
 		&app.serve,
 		&version{app: app},
 		&help{app: app},
@@ -305,14 +303,14 @@
 	}
 }
 
-func (app *Application) internalCommands() []tool.Command {
-	return []tool.Command{
+func (app *application) internalCommands() []command {
+	return []command{
 		&vulncheck{app: app},
 	}
 }
 
-func (app *Application) featureCommands() []tool.Command {
-	return []tool.Command{
+func (app *application) featureCommands() []command {
+	return []command{
 		&callHierarchy{app: app},
 		&check{app: app, Severity: "warning"},
 		&codeaction{app: app},
@@ -340,7 +338,8 @@
 }
 
 // connect creates and initializes a new in-process gopls LSP session.
-func (app *Application) connect(ctx context.Context) (*client, *cache.Session, error) {
+func (app *application) connect(ctx context.Context) (*client, *cache.Session, error) {
+
 	root, err := os.Getwd()
 	if err != nil {
 		return nil, nil, fmt.Errorf("finding workdir: %v", err)
@@ -432,7 +431,7 @@
 // connection; it conceptually corresponds to a single call to
 // connect(2).
 type client struct {
-	app *Application
+	app *application
 
 	server           protocol.Server
 	initializeResult *protocol.InitializeResult // includes server capabilities
@@ -454,7 +453,7 @@
 	diagnostics   []protocol.Diagnostic
 }
 
-func newClient(app *Application) *client {
+func newClient(app *application) *client {
 	return &client{
 		app:     app,
 		files:   make(map[protocol.DocumentURI]*cmdFile),
@@ -850,7 +849,7 @@
 }
 
 func diagnoseFiles(ctx context.Context, server protocol.Server, files []protocol.DocumentURI) error {
-	cmd := command.NewDiagnoseFilesCommand("Diagnose files", command.DiagnoseFilesArgs{
+	cmd := protocolcommand.NewDiagnoseFilesCommand("Diagnose files", protocolcommand.DiagnoseFilesArgs{
 		Files: files,
 	})
 	_, err := executeCommand(ctx, server, cmd)
diff --git a/gopls/internal/cmd/codeaction.go b/gopls/internal/cmd/codeaction.go
index 974b499..f15ccdf 100644
--- a/gopls/internal/cmd/codeaction.go
+++ b/gopls/internal/cmd/codeaction.go
@@ -13,7 +13,6 @@
 	"strings"
 
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // codeaction implements the codeaction verb for gopls.
@@ -23,7 +22,7 @@
 	Title string `flag:"title" help:"regular expression to match title"`
 	Exec  bool   `flag:"exec" help:"execute the first matching code action"`
 
-	app *Application
+	app *application
 }
 
 func (cmd *codeaction) Name() string      { return "codeaction" }
@@ -105,7 +104,7 @@
 
 func (cmd *codeaction) Run(ctx context.Context, args ...string) error {
 	if len(args) < 1 {
-		return tool.CommandLineErrorf("codeaction expects at least 1 argument")
+		return commandLineErrorf("codeaction expects at least 1 argument")
 	}
 	cmd.app.editFlags = &cmd.EditFlags
 	cli, _, err := cmd.app.connect(ctx)
diff --git a/gopls/internal/cmd/codelens.go b/gopls/internal/cmd/codelens.go
index 1d32493..e48462a 100644
--- a/gopls/internal/cmd/codelens.go
+++ b/gopls/internal/cmd/codelens.go
@@ -11,13 +11,12 @@
 
 	"golang.org/x/tools/gopls/internal/protocol"
 	"golang.org/x/tools/gopls/internal/settings"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // codelens implements the codelens verb for gopls.
 type codelens struct {
 	EditFlags
-	app *Application
+	app *application
 
 	Exec bool `flag:"exec" help:"execute the first matching code lens"`
 }
@@ -56,14 +55,14 @@
 	var filename, title string
 	switch len(args) {
 	case 0:
-		return tool.CommandLineErrorf("codelens requires a file name")
+		return commandLineErrorf("codelens requires a file name")
 	case 2:
 		title = args[1]
 		fallthrough
 	case 1:
 		filename = args[0]
 	default:
-		return tool.CommandLineErrorf("codelens expects at most two arguments")
+		return commandLineErrorf("codelens expects at most two arguments")
 	}
 
 	r.app.editFlags = &r.EditFlags // in case a codelens perform an edit
diff --git a/gopls/internal/cmd/definition.go b/gopls/internal/cmd/definition.go
index 26dd86f..99fe5dc 100644
--- a/gopls/internal/cmd/definition.go
+++ b/gopls/internal/cmd/definition.go
@@ -14,11 +14,10 @@
 
 	"golang.org/x/tools/gopls/internal/protocol"
 	"golang.org/x/tools/gopls/internal/settings"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
-// A Definition is the result of a 'definition' query.
-type Definition struct {
+// A definitionJSON is the result of a 'definition' query.
+type definitionJSON struct {
 	Span        span   `json:"span"`        // span of the definition
 	Description string `json:"description"` // description of the denoted object
 }
@@ -34,7 +33,7 @@
 
 // definition implements the definition verb for gopls.
 type definition struct {
-	app *Application
+	app *application
 
 	JSON              bool `flag:"json" help:"emit output in JSON format"`
 	MarkdownSupported bool `flag:"markdown" help:"support markdown in responses"`
@@ -60,7 +59,7 @@
 // results to stdout.
 func (d *definition) Run(ctx context.Context, args ...string) error {
 	if len(args) != 1 {
-		return tool.CommandLineErrorf("definition expects 1 argument")
+		return commandLineErrorf("definition expects 1 argument")
 	}
 	// Plaintext makes more sense for the command line.
 	opts := d.app.options
@@ -119,7 +118,7 @@
 		description = strings.TrimSpace(hover.Contents.Value)
 	}
 
-	result := &Definition{
+	result := &definitionJSON{
 		Span:        definition,
 		Description: description,
 	}
diff --git a/gopls/internal/cmd/execute.go b/gopls/internal/cmd/execute.go
index 0c40e59..c139635 100644
--- a/gopls/internal/cmd/execute.go
+++ b/gopls/internal/cmd/execute.go
@@ -15,15 +15,14 @@
 
 	"golang.org/x/tools/gopls/internal/filecache"
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/protocol/command"
-	"golang.org/x/tools/gopls/internal/tool"
+	protocolcommand "golang.org/x/tools/gopls/internal/protocol/command"
 	"golang.org/x/tools/gopls/internal/util/bug"
 )
 
 // execute implements the LSP ExecuteCommand verb for gopls.
 type execute struct {
 	EditFlags
-	app *Application
+	app *application
 }
 
 func (e *execute) Name() string      { return "execute" }
@@ -48,6 +47,7 @@
 
 execute-flags:
 `)
+
 	printFlagDefaults(f)
 }
 
@@ -62,11 +62,11 @@
 	}
 
 	if len(args) == 0 {
-		return tool.CommandLineErrorf("execute requires a command name")
+		return commandLineErrorf("execute requires a command name")
 	}
 	cmd := args[0]
-	if !slices.Contains(command.Commands, command.Command(cmd)) {
-		return tool.CommandLineErrorf("unrecognized command: %s", cmd)
+	if !slices.Contains(protocolcommand.Commands, protocolcommand.Command(cmd)) {
+		return commandLineErrorf("unrecognized command: %s", cmd)
 	}
 
 	// A command may have multiple arguments, though the only one
diff --git a/gopls/internal/cmd/export_test.go b/gopls/internal/cmd/export_test.go
new file mode 100644
index 0000000..800a9d7
--- /dev/null
+++ b/gopls/internal/cmd/export_test.go
@@ -0,0 +1,24 @@
+// Copyright 2026 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// This file exports unexported internal symbols exclusively
+// for use in package cmd_test.
+
+package cmd
+
+type (
+	DefinitionJSON = definitionJSON
+	StatsJSON      = statsJSON
+)
+
+// CommandNames returns the names of all commands, including the root command.
+func CommandNames() []string {
+	var names []string
+	app := newApplication()
+	for _, c := range app.Commands() {
+		names = append(names, c.Name())
+	}
+	names = append(names, app.Name())
+	return names
+}
diff --git a/gopls/internal/cmd/folding_range.go b/gopls/internal/cmd/folding_range.go
index e2c2f4b..37c9617 100644
--- a/gopls/internal/cmd/folding_range.go
+++ b/gopls/internal/cmd/folding_range.go
@@ -10,12 +10,11 @@
 	"fmt"
 
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // foldingRanges implements the folding_ranges verb for gopls
 type foldingRanges struct {
-	app *Application
+	app *application
 }
 
 func (r *foldingRanges) Name() string      { return "folding_ranges" }
@@ -33,7 +32,7 @@
 
 func (r *foldingRanges) Run(ctx context.Context, args ...string) error {
 	if len(args) != 1 {
-		return tool.CommandLineErrorf("folding_ranges expects 1 argument (file)")
+		return commandLineErrorf("folding_ranges expects 1 argument (file)")
 	}
 
 	cli, _, err := r.app.connect(ctx)
diff --git a/gopls/internal/cmd/format.go b/gopls/internal/cmd/format.go
index 8766d9c..16da4ff 100644
--- a/gopls/internal/cmd/format.go
+++ b/gopls/internal/cmd/format.go
@@ -15,7 +15,7 @@
 // format implements the format verb for gopls.
 type format struct {
 	EditFlags
-	app *Application
+	app *application
 }
 
 func (c *format) Name() string      { return "format" }
diff --git a/gopls/internal/tool/tool.go b/gopls/internal/cmd/harness.go
similarity index 74%
rename from gopls/internal/tool/tool.go
rename to gopls/internal/cmd/harness.go
index 53031ea..3873bbf 100644
--- a/gopls/internal/tool/tool.go
+++ b/gopls/internal/cmd/harness.go
@@ -2,8 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Package tool is a harness for writing Go tools.
-package tool
+package cmd
 
 import (
 	"context"
@@ -19,21 +18,12 @@
 	"time"
 )
 
-// This file is a harness for writing your main function.
-//
-// It recursively scans the command object for fields with a tag containing
-//     `flag:"flagnames" help:"short help text"`
-// uses all those fields to build command line flags. It will split flagnames on
-// commas and add a flag per name.
-// It expects the Command type to have a method
-//     Run(context.Context, args...string) error
-// which it invokes only after all command line flag processing has been finished.
-// If Run returns an error, the error will be printed to stderr and the
-// application will quit with a non zero exit status.
+// This file defines common flags and helper functions
+// that coordinate flag registration via reflection.
 
-// Profile can be embedded in your application struct to automatically
-// add command line arguments and handling for the common profiling methods.
-type Profile struct {
+// ProfileFlags can be embedded in your application struct to automatically
+// add command line arguments and handling for common profiling methods.
+type ProfileFlags struct {
 	CPU    string `flag:"profile.cpu" help:"write CPU profile to this file"`
 	Memory string `flag:"profile.mem" help:"write memory profile to this file"`
 	Alloc  string `flag:"profile.alloc" help:"write alloc profile to this file"`
@@ -41,8 +31,8 @@
 	Block  string `flag:"profile.block" help:"write block profile to this file"`
 }
 
-// Command is the interface that must be satisfied by an object passed to Main.
-type Command interface {
+// command represents an executable CLI command or subcommand within gopls.
+type command interface {
 	// Name returns the command's name. It is used in help and error messages.
 	Name() string
 	// Most of the help usage is automatically generated, this string should only
@@ -61,21 +51,24 @@
 	Run(ctx context.Context, args ...string) error
 }
 
-type Subcommand interface {
-	Command
+type subcommand interface {
+	// TODO(hyangah): merge with command. It is unclear why we need
+	// to keep command and subcommand separate.
+
+	command
 	Parent() string
 }
 
-// This is the type returned by CommandLineErrorf, which causes the outer main
+// This is the type returned by commandLineErrorf, which causes the outer main
 // to trigger printing of the command line help.
 type commandLineError string
 
 func (e commandLineError) Error() string { return string(e) }
 
-// CommandLineErrorf is like fmt.Errorf except that it returns a value that
+// commandLineErrorf is like fmt.Errorf except that it returns a value that
 // triggers printing of the command line help.
 // In general you should use this when generating command line validation errors.
-func CommandLineErrorf(message string, args ...any) error {
+func commandLineErrorf(message string, args ...any) error {
 	return commandLineError(fmt.Sprintf(message, args...))
 }
 
@@ -83,9 +76,12 @@
 // It will only return if there was no error.  If an error
 // was encountered it is printed to standard error and the
 // application exits with an exit code of 2.
-func Main(ctx context.Context, app Command, args []string) {
+func Main() {
+	ctx := context.Background()
+	args := os.Args[1:]
+	app := newApplication()
 	s := flag.NewFlagSet(app.Name(), flag.ExitOnError)
-	if err := Run(ctx, s, app, args); err != nil {
+	if err := runCommand(ctx, s, app, args); err != nil {
 		fmt.Fprintf(s.Output(), "%s: %v\n", app.Name(), err)
 		if _, printHelp := err.(commandLineError); printHelp {
 			// TODO(adonovan): refine this. It causes
@@ -98,27 +94,30 @@
 	}
 }
 
-// Run is the inner loop for Main; invoked by Main, recursively by
-// Run, and by various tests.  It runs the application and returns an
-// error.
-func Run(ctx context.Context, s *flag.FlagSet, app Command, args []string) (resultErr error) {
-	s.Usage = func() {
-		if app.ShortHelp() != "" {
-			fmt.Fprintf(s.Output(), "%s\n\nUsage:\n  ", app.ShortHelp())
-			if sub, ok := app.(Subcommand); ok && sub.Parent() != "" {
-				fmt.Fprintf(s.Output(), "%s [flags] %s", sub.Parent(), app.Name())
-			} else {
-				fmt.Fprintf(s.Output(), "%s [flags]", app.Name())
-			}
-			if usage := app.Usage(); usage != "" {
-				fmt.Fprintf(s.Output(), " %s", usage)
-			}
-			fmt.Fprint(s.Output(), "\n")
+// printHelp prints the usage and detailed help for any command to s.Output().
+func printHelp(s *flag.FlagSet, app command) {
+	if app.ShortHelp() != "" {
+		fmt.Fprintf(s.Output(), "%s\n\nUsage:\n  ", app.ShortHelp())
+		if sub, ok := app.(subcommand); ok && sub.Parent() != "" {
+			fmt.Fprintf(s.Output(), "%s [flags] %s", sub.Parent(), app.Name())
+		} else {
+			fmt.Fprintf(s.Output(), "%s [flags]", app.Name())
 		}
-		app.DetailedHelp(s)
+		if usage := app.Usage(); usage != "" {
+			fmt.Fprintf(s.Output(), " %s", usage)
+		}
+		fmt.Fprintln(s.Output())
 	}
-	p := addFlags(s, reflect.StructField{}, reflect.ValueOf(app))
-	if err := s.Parse(args); err != nil {
+	app.DetailedHelp(s)
+}
+
+// runCommand executes cmd with the provided flagset and arguments.
+func runCommand(ctx context.Context, flags *flag.FlagSet, cmd command, args []string) (resultErr error) {
+	flags.Usage = func() { printHelp(flags, cmd) }
+	// addFlags returns non-nil *ProfileFlags only if cmd embeds ProfileFlags.
+	// Only 'application' meets this criteria.
+	p := addFlags(flags, reflect.StructField{}, reflect.ValueOf(cmd))
+	if err := flags.Parse(args); err != nil {
 		return err
 	}
 
@@ -204,12 +203,12 @@
 		}()
 	}
 
-	return app.Run(ctx, s.Args()...)
+	return cmd.Run(ctx, flags.Args()...)
 }
 
 // addFlags scans fields of structs recursively to find things with flag tags
 // and add them to the flag set.
-func addFlags(f *flag.FlagSet, field reflect.StructField, value reflect.Value) *Profile {
+func addFlags(f *flag.FlagSet, field reflect.StructField, value reflect.Value) *ProfileFlags {
 	// is it a field we are allowed to reflect on?
 	if field.PkgPath != "" {
 		return nil
@@ -238,7 +237,7 @@
 
 	// TODO(adonovan): there's no need for this special treatment of Profile:
 	// The caller can use f.Lookup("profile.cpu") etc instead.
-	p, _ := value.Addr().Interface().(*Profile)
+	p, _ := value.Addr().Interface().(*ProfileFlags)
 	// go through all the fields of the struct
 	for i := 0; i < value.Type().NumField(); i++ {
 		child := value.Type().Field(i)
diff --git a/gopls/internal/cmd/help_test.go b/gopls/internal/cmd/help_test.go
index 43f9daf..3518dce 100644
--- a/gopls/internal/cmd/help_test.go
+++ b/gopls/internal/cmd/help_test.go
@@ -29,15 +29,13 @@
 func TestHelpFiles(t *testing.T) {
 	testenv.NeedsGoBuild(t) // This is a lie. We actually need the source code.
 	t.Parallel()
-	app := cmd.New()
 	tree := writeTree(t, "")
-	for _, cmd := range append(app.Commands(), app) {
-		name := cmd.Name()
+	for _, name := range cmd.CommandNames() {
 		t.Run(name, func(t *testing.T) {
 			t.Parallel()
 			args := []string{name, "-h"}
 			// The output of 'gopls -h' is in usage.hlp
-			if cmd == app {
+			if name == "gopls" {
 				args = args[1:]
 				name = "usage"
 			}
diff --git a/gopls/internal/cmd/highlight.go b/gopls/internal/cmd/highlight.go
index 26c7fe6..6675336 100644
--- a/gopls/internal/cmd/highlight.go
+++ b/gopls/internal/cmd/highlight.go
@@ -10,12 +10,11 @@
 	"fmt"
 
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // highlight implements the highlight verb for gopls.
 type highlight struct {
-	app *Application
+	app *application
 }
 
 func (r *highlight) Name() string      { return "highlight" }
@@ -35,7 +34,7 @@
 
 func (r *highlight) Run(ctx context.Context, args ...string) error {
 	if len(args) != 1 {
-		return tool.CommandLineErrorf("highlight expects 1 argument (position)")
+		return commandLineErrorf("highlight expects 1 argument (position)")
 	}
 
 	cli, _, err := r.app.connect(ctx)
diff --git a/gopls/internal/cmd/implementation.go b/gopls/internal/cmd/implementation.go
index 0c845f4..8332603 100644
--- a/gopls/internal/cmd/implementation.go
+++ b/gopls/internal/cmd/implementation.go
@@ -11,12 +11,11 @@
 	"sort"
 
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // implementation implements the implementation verb for gopls
 type implementation struct {
-	app *Application
+	app *application
 }
 
 func (i *implementation) Name() string      { return "implementation" }
@@ -36,7 +35,7 @@
 
 func (i *implementation) Run(ctx context.Context, args ...string) error {
 	if len(args) != 1 {
-		return tool.CommandLineErrorf("implementation expects 1 argument (position)")
+		return commandLineErrorf("implementation expects 1 argument (position)")
 	}
 
 	cli, _, err := i.app.connect(ctx)
diff --git a/gopls/internal/cmd/imports.go b/gopls/internal/cmd/imports.go
index 0b8f143..8a29976 100644
--- a/gopls/internal/cmd/imports.go
+++ b/gopls/internal/cmd/imports.go
@@ -10,13 +10,12 @@
 	"fmt"
 
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // imports implements the import verb for gopls.
 type imports struct {
 	EditFlags
-	app *Application
+	app *application
 }
 
 func (t *imports) Name() string      { return "imports" }
@@ -40,7 +39,7 @@
 // - otherwise, prints the new versions to stdout.
 func (t *imports) Run(ctx context.Context, args ...string) error {
 	if len(args) != 1 {
-		return tool.CommandLineErrorf("imports expects 1 argument")
+		return commandLineErrorf("imports expects 1 argument")
 	}
 	t.app.editFlags = &t.EditFlags
 	cli, _, err := t.app.connect(ctx)
diff --git a/gopls/internal/cmd/info.go b/gopls/internal/cmd/info.go
index dd4f991..7dd6237 100644
--- a/gopls/internal/cmd/info.go
+++ b/gopls/internal/cmd/info.go
@@ -18,12 +18,11 @@
 	"golang.org/x/tools/gopls/internal/debug"
 	"golang.org/x/tools/gopls/internal/doc"
 	licensespkg "golang.org/x/tools/gopls/internal/licenses"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // help implements the help command.
 type help struct {
-	app *Application
+	app *application
 }
 
 func (h *help) Name() string      { return "help" }
@@ -43,7 +42,7 @@
 
 // Run prints help information about a subcommand.
 func (h *help) Run(ctx context.Context, args ...string) error {
-	find := func(cmds []tool.Command, name string) tool.Command {
+	find := func(cmds []command, name string) command {
 		for _, cmd := range cmds {
 			if cmd.Name() == name {
 				return cmd
@@ -53,27 +52,27 @@
 	}
 
 	// Find the subcommand denoted by args (empty => h.app).
-	var cmd tool.Command = h.app
+	var cmd command = h.app
 	for i, arg := range args {
 		cmd = find(getSubcommands(cmd), arg)
 		if cmd == nil {
-			return tool.CommandLineErrorf(
+			return commandLineErrorf(
 				"no such subcommand: %s", strings.Join(args[:i+1], " "))
 		}
 	}
 
 	// 'gopls help cmd subcmd' is equivalent to 'gopls cmd subcmd -h'.
-	// The flag package prints the usage information (defined by tool.Run)
+	// The flag package prints the usage information (defined by Run)
 	// when it sees the -h flag.
 	fs := flag.NewFlagSet(cmd.Name(), flag.ExitOnError)
-	return tool.Run(ctx, fs, h.app, append(args[:len(args):len(args)], "-h"))
+	return runCommand(ctx, fs, h.app, append(args[:len(args):len(args)], "-h"))
 }
 
 // version implements the version command.
 type version struct {
 	JSON bool `flag:"json" help:"outputs in json format."`
 
-	app *Application
+	app *application
 }
 
 func (v *version) Name() string      { return "version" }
@@ -98,7 +97,7 @@
 }
 
 type apiJSON struct {
-	app *Application
+	app *application
 }
 
 func (j *apiJSON) Name() string      { return "api-json" }
@@ -121,7 +120,7 @@
 }
 
 type licenses struct {
-	app *Application
+	app *application
 }
 
 func (l *licenses) Name() string      { return "licenses" }
diff --git a/gopls/internal/cmd/integration_test.go b/gopls/internal/cmd/integration_test.go
index 170629c..ff4b353 100644
--- a/gopls/internal/cmd/integration_test.go
+++ b/gopls/internal/cmd/integration_test.go
@@ -27,7 +27,6 @@
 
 import (
 	"bytes"
-	"context"
 	"encoding/json"
 	"fmt"
 	"math/rand"
@@ -41,7 +40,6 @@
 	"golang.org/x/tools/gopls/internal/cmd"
 	"golang.org/x/tools/gopls/internal/debug"
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/tool"
 	"golang.org/x/tools/gopls/internal/util/bug"
 	"golang.org/x/tools/gopls/internal/version"
 	"golang.org/x/tools/internal/testenv"
@@ -84,6 +82,17 @@
 	}
 }
 
+func TestProfileFlags(t *testing.T) {
+	t.Parallel()
+	tree := writeTree(t, "")
+	cpuProfile := filepath.Join(t.TempDir(), "cpu.prof")
+	res := gopls(t, tree, "-profile.cpu="+cpuProfile, "version")
+	res.checkExit(true)
+	if info, err := os.Stat(cpuProfile); err != nil || info.Size() == 0 {
+		t.Errorf("expected non-empty profile file %s, got err=%v", cpuProfile, err)
+	}
+}
+
 // TestCheck tests the 'check' subcommand (check.go).
 func TestCheck(t *testing.T) {
 	t.Parallel()
@@ -302,7 +311,7 @@
 	{
 		res := gopls(t, tree, "definition", "-json", "-markdown", "a.go:4:7")
 		res.checkExit(true)
-		var defn cmd.Definition
+		var defn cmd.DefinitionJSON
 		if res.toJSON(&defn) {
 			if !strings.HasPrefix(defn.Description, "```go\nfunc fmt.Println") {
 				t.Errorf("Description does not start with markdown code block. Got: %s", defn.Description)
@@ -878,7 +887,7 @@
 	res := gopls(t, tree, "stats")
 	res.checkExit(true)
 
-	var stats cmd.GoplsStats
+	var stats cmd.StatsJSON
 	if err := json.Unmarshal([]byte(res.stdout), &stats); err != nil {
 		t.Fatalf("failed to unmarshal JSON output of stats command: %v", err)
 	}
@@ -930,7 +939,7 @@
 		res2 := gopls(t, tree, "stats", "-anon")
 		res2.checkExit(true)
 
-		var stats2 cmd.GoplsStats
+		var stats2 cmd.StatsJSON
 		if err := json.Unmarshal([]byte(res2.stdout), &stats2); err != nil {
 			t.Fatalf("failed to unmarshal JSON output of stats command: %v", err)
 		}
@@ -1116,7 +1125,7 @@
 		version.VersionOverride = v
 	}
 
-	tool.Main(context.Background(), cmd.New(), os.Args[1:])
+	cmd.Main()
 }
 
 // writeTree extracts a txtar archive into a new directory and returns its path.
diff --git a/gopls/internal/cmd/links.go b/gopls/internal/cmd/links.go
index 3b08868..4681793 100644
--- a/gopls/internal/cmd/links.go
+++ b/gopls/internal/cmd/links.go
@@ -12,14 +12,13 @@
 	"os"
 
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // links implements the links verb for gopls.
 type links struct {
 	JSON bool `flag:"json" help:"emit document links in JSON format"`
 
-	app *Application
+	app *application
 }
 
 func (l *links) Name() string      { return "links" }
@@ -42,7 +41,7 @@
 // - otherwise, prints the a list of unique links
 func (l *links) Run(ctx context.Context, args ...string) error {
 	if len(args) != 1 {
-		return tool.CommandLineErrorf("links expects 1 argument")
+		return commandLineErrorf("links expects 1 argument")
 	}
 	cli, _, err := l.app.connect(ctx)
 	if err != nil {
diff --git a/gopls/internal/cmd/mcp.go b/gopls/internal/cmd/mcp.go
index a35f049..1175ae0 100644
--- a/gopls/internal/cmd/mcp.go
+++ b/gopls/internal/cmd/mcp.go
@@ -21,7 +21,7 @@
 )
 
 type headlessMCP struct {
-	app *Application
+	app *application
 
 	Address      string `flag:"listen" help:"the address on which to run the mcp server"`
 	Logfile      string `flag:"logfile" help:"filename to log to; if unset, logs to stderr"`
@@ -47,7 +47,12 @@
 }
 
 func (m *headlessMCP) Run(ctx context.Context, args ...string) error {
+	// TODO(hxjiang): properly support remote mode (https://github.com/golang/go/issues/78668).
+	if m.app.Remote != "" {
+		return commandLineErrorf("mcp does not currently support remote mode")
+	}
 	if m.Instructions {
+
 		fmt.Println(internalmcp.Instructions)
 		return nil
 	}
diff --git a/gopls/internal/cmd/prepare_rename.go b/gopls/internal/cmd/prepare_rename.go
index 72f42af..715f1fd 100644
--- a/gopls/internal/cmd/prepare_rename.go
+++ b/gopls/internal/cmd/prepare_rename.go
@@ -11,12 +11,11 @@
 	"fmt"
 
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // prepareRename implements the prepare_rename verb for gopls.
 type prepareRename struct {
-	app *Application
+	app *application
 }
 
 func (r *prepareRename) Name() string      { return "prepare_rename" }
@@ -34,13 +33,13 @@
 	printFlagDefaults(f)
 }
 
-// ErrInvalidRenamePosition is returned when prepareRename is run at a position that
+// errInvalidRenamePosition is returned when prepareRename is run at a position that
 // is not a candidate for renaming.
-var ErrInvalidRenamePosition = errors.New("request is not valid at the given position")
+var errInvalidRenamePosition = errors.New("request is not valid at the given position")
 
 func (r *prepareRename) Run(ctx context.Context, args ...string) error {
 	if len(args) != 1 {
-		return tool.CommandLineErrorf("prepare_rename expects 1 argument (file)")
+		return commandLineErrorf("prepare_rename expects 1 argument (file)")
 	}
 
 	cli, _, err := r.app.connect(ctx)
@@ -66,7 +65,7 @@
 		return fmt.Errorf("prepare_rename failed: %w", err)
 	}
 	if result == nil {
-		return ErrInvalidRenamePosition
+		return errInvalidRenamePosition
 	}
 
 	s, err := file.rangeSpan(result.Range)
diff --git a/gopls/internal/cmd/references.go b/gopls/internal/cmd/references.go
index a91925f..def3f0b 100644
--- a/gopls/internal/cmd/references.go
+++ b/gopls/internal/cmd/references.go
@@ -11,14 +11,13 @@
 	"sort"
 
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // references implements the references verb for gopls
 type references struct {
 	IncludeDeclaration bool `flag:"d,declaration" help:"include the declaration of the specified identifier in the results"`
 
-	app *Application
+	app *application
 }
 
 func (r *references) Name() string      { return "references" }
@@ -40,7 +39,7 @@
 
 func (r *references) Run(ctx context.Context, args ...string) error {
 	if len(args) != 1 {
-		return tool.CommandLineErrorf("references expects 1 argument (position)")
+		return commandLineErrorf("references expects 1 argument (position)")
 	}
 
 	cli, _, err := r.app.connect(ctx)
diff --git a/gopls/internal/cmd/remote.go b/gopls/internal/cmd/remote.go
index c100164..65614d3 100644
--- a/gopls/internal/cmd/remote.go
+++ b/gopls/internal/cmd/remote.go
@@ -14,15 +14,15 @@
 	"os"
 
 	"golang.org/x/tools/gopls/internal/lsprpc"
-	"golang.org/x/tools/gopls/internal/protocol/command"
+	protocolcommand "golang.org/x/tools/gopls/internal/protocol/command"
 )
 
 type remote struct {
-	app *Application
+	app *application
 	subcommands
 }
 
-func newRemote(app *Application) *remote {
+func newRemote(app *application) *remote {
 	return &remote{
 		app: app,
 		subcommands: subcommands{
@@ -44,7 +44,7 @@
 
 // listSessions is an inspect subcommand to list current sessions.
 type listSessions struct {
-	app *Application
+	app *application
 }
 
 func (c *listSessions) Name() string   { return "sessions" }
@@ -91,7 +91,7 @@
 }
 
 type startDebugging struct {
-	app *Application
+	app *application
 }
 
 func (c *startDebugging) Name() string  { return "debug" }
@@ -132,11 +132,11 @@
 	if len(args) > 0 {
 		debugAddr = args[0]
 	}
-	debugArgs := command.DebuggingArgs{
+	debugArgs := protocolcommand.DebuggingArgs{
 		Addr: debugAddr,
 	}
-	var result command.DebuggingResult
-	if err := lsprpc.ExecuteCommand(ctx, remote, command.StartDebugging.String(), debugArgs, &result); err != nil {
+	var result protocolcommand.DebuggingResult
+	if err := lsprpc.ExecuteCommand(ctx, remote, protocolcommand.StartDebugging.String(), debugArgs, &result); err != nil {
 		return err
 	}
 	if len(result.URLs) == 0 {
diff --git a/gopls/internal/cmd/rename.go b/gopls/internal/cmd/rename.go
index 1922f40..6afdedd 100644
--- a/gopls/internal/cmd/rename.go
+++ b/gopls/internal/cmd/rename.go
@@ -10,13 +10,12 @@
 	"fmt"
 
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // rename implements the rename verb for gopls.
 type rename struct {
 	EditFlags
-	app *Application
+	app *application
 }
 
 func (r *rename) Name() string      { return "rename" }
@@ -42,7 +41,7 @@
 // - otherwise, prints the new versions to stdout.
 func (r *rename) Run(ctx context.Context, args ...string) error {
 	if len(args) != 2 {
-		return tool.CommandLineErrorf("rename expects 2 arguments (position, new name)")
+		return commandLineErrorf("rename expects 2 arguments (position, new name)")
 	}
 	r.app.editFlags = &r.EditFlags
 	cli, _, err := r.app.connect(ctx)
diff --git a/gopls/internal/cmd/semantictokens.go b/gopls/internal/cmd/semantictokens.go
index 6dd0052..c971945 100644
--- a/gopls/internal/cmd/semantictokens.go
+++ b/gopls/internal/cmd/semantictokens.go
@@ -43,7 +43,7 @@
 //      the gopls coordinate system
 
 type semanticToken struct {
-	app *Application
+	app *application
 }
 
 func (c *semanticToken) Name() string      { return "semtok" }
@@ -114,8 +114,8 @@
 
 // prefixes for semantic token comments
 const (
-	SemanticLeft  = "/*⇐"
-	SemanticRight = "/*⇒"
+	semanticLeft  = "/*⇐"
+	semanticRight = "/*⇒"
 )
 
 func markLine(m mark, lines [][]byte) {
@@ -126,7 +126,7 @@
 	if m.typ == "namespace" && m.offset-1+m.len < len(l) && l[m.offset-1+m.len] == '"' {
 		// it is the last component of an import spec
 		// cannot put a comment inside a string
-		insert = fmt.Sprintf("%s%d,namespace,[]*/", SemanticLeft, length)
+		insert = fmt.Sprintf("%s%d,namespace,[]*/", semanticLeft, length)
 		splitAt = m.offset + m.len
 	} else {
 		// be careful not to generate //*
@@ -134,7 +134,7 @@
 		if splitAt-1 >= 0 && l[splitAt-1] == '/' {
 			spacer = " "
 		}
-		insert = fmt.Sprintf("%s%s%d,%s,%v*/", spacer, SemanticRight, length, m.typ, m.mods)
+		insert = fmt.Sprintf("%s%s%d,%s,%v*/", spacer, semanticRight, length, m.typ, m.mods)
 	}
 	x := append([]byte(insert), l[splitAt:]...)
 	l = append(l[:splitAt], x...)
diff --git a/gopls/internal/cmd/serve.go b/gopls/internal/cmd/serve.go
index 3861d62..7df303b 100644
--- a/gopls/internal/cmd/serve.go
+++ b/gopls/internal/cmd/serve.go
@@ -21,14 +21,13 @@
 	"golang.org/x/tools/gopls/internal/lsprpc"
 	"golang.org/x/tools/gopls/internal/mcp"
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/tool"
 	"golang.org/x/tools/gopls/internal/util/fakenet"
 	"golang.org/x/tools/internal/jsonrpc2"
 )
 
-// Serve is a struct that exposes the configurable parts of the LSP and MCP
+// serve is a struct that exposes the configurable parts of the LSP and MCP
 // server as flags, in the right form for tool.Main to consume.
-type Serve struct {
+type serve struct {
 	Logfile     string        `flag:"logfile" help:"filename to log to. if value is \"auto\", then logging to a default output file is enabled"`
 	Mode        string        `flag:"mode" help:"no effect"`
 	Address     string        `flag:"listen" help:"address on which to listen for remote connections. If prefixed by 'unix;', the subsequent address is assumed to be a unix domain socket. Otherwise, TCP is used."`
@@ -39,16 +38,16 @@
 	// MCP Server related configurations.
 	MCPAddress string `flag:"mcp.listen" help:"experimental: address on which to listen for model context protocol connections. If port is localhost:0, pick a random port in localhost instead."`
 
-	app *Application
+	app *application
 }
 
-func (s *Serve) Name() string   { return "serve" }
-func (s *Serve) Parent() string { return s.app.Name() }
-func (s *Serve) Usage() string  { return "[server-flags]" }
-func (s *Serve) ShortHelp() string {
+func (s *serve) Name() string   { return "serve" }
+func (s *serve) Parent() string { return s.app.Name() }
+func (s *serve) Usage() string  { return "[server-flags]" }
+func (s *serve) ShortHelp() string {
 	return "run a server for Go code using the Language Server Protocol"
 }
-func (s *Serve) DetailedHelp(f *flag.FlagSet) {
+func (s *serve) DetailedHelp(f *flag.FlagSet) {
 	fmt.Fprint(f.Output(), `  gopls [flags] [server-flags]
 
 The server communicates using JSONRPC2 on stdin and stdout, and is intended to be run directly as
@@ -61,9 +60,9 @@
 
 // Run configures a server based on the flags, and then runs it.
 // It blocks until the server shuts down.
-func (s *Serve) Run(ctx context.Context, args ...string) error {
+func (s *serve) Run(ctx context.Context, args ...string) error {
 	if len(args) > 0 {
-		return tool.CommandLineErrorf("server does not take arguments, got %v", args)
+		return commandLineErrorf("server does not take arguments, got %v", args)
 	}
 
 	di := debug.GetInstance(ctx)
diff --git a/gopls/internal/cmd/signature.go b/gopls/internal/cmd/signature.go
index e1c5372..9f62afd 100644
--- a/gopls/internal/cmd/signature.go
+++ b/gopls/internal/cmd/signature.go
@@ -10,12 +10,11 @@
 	"fmt"
 
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // signature implements the signature verb for gopls
 type signature struct {
-	app *Application
+	app *application
 }
 
 func (r *signature) Name() string      { return "signature" }
@@ -35,7 +34,7 @@
 
 func (r *signature) Run(ctx context.Context, args ...string) error {
 	if len(args) != 1 {
-		return tool.CommandLineErrorf("signature expects 1 argument (position)")
+		return commandLineErrorf("signature expects 1 argument (position)")
 	}
 
 	cli, _, err := r.app.connect(ctx)
@@ -65,7 +64,7 @@
 	}
 
 	if s == nil || len(s.Signatures) == 0 {
-		return tool.CommandLineErrorf("%v: not a function", from)
+		return commandLineErrorf("%v: not a function", from)
 	}
 
 	// there is only ever one possible signature,
diff --git a/gopls/internal/cmd/stats.go b/gopls/internal/cmd/stats.go
index 2fad301..af8c77a 100644
--- a/gopls/internal/cmd/stats.go
+++ b/gopls/internal/cmd/stats.go
@@ -20,7 +20,7 @@
 
 	"golang.org/x/tools/gopls/internal/filecache"
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/protocol/command"
+	protocolcommand "golang.org/x/tools/gopls/internal/protocol/command"
 	"golang.org/x/tools/gopls/internal/settings"
 	"golang.org/x/tools/gopls/internal/util/bug"
 	versionpkg "golang.org/x/tools/gopls/internal/version"
@@ -28,7 +28,7 @@
 )
 
 type stats struct {
-	app *Application
+	app *application
 
 	Anon bool `flag:"anon" help:"hide any fields that may contain user names, file names, or source code"`
 }
@@ -55,19 +55,21 @@
 }
 
 func (s *stats) Run(ctx context.Context, args ...string) error {
+	// stats does not work with -remote yet.
+	// Other sessions on the daemon may interfere with results.
+	// Additionally, the type assertions in below only work if progress
+	// notifications bypass jsonrpc2 serialization.
+	// TODO(hyangah): support remote mode by fetching server-side
+	// statistics and avoiding hangs during InitialWorkspaceLoad.
 	if s.app.Remote != "" {
-		// stats does not work with -remote.
-		// Other sessions on the daemon may interfere with results.
-		// Additionally, the type assertions in below only work if progress
-		// notifications bypass jsonrpc2 serialization.
-		return fmt.Errorf("the stats subcommand does not work with -remote")
+		return commandLineErrorf("stats does not currently support remote mode")
 	}
 
 	if !s.app.Verbose {
 		event.SetExporter(nil) // don't log errors to stderr
 	}
 
-	stats := GoplsStats{
+	stats := statsJSON{
 		GOOS:             runtime.GOOS,
 		GOARCH:           runtime.GOARCH,
 		GOPLSCACHE:       os.Getenv("GOPLSCACHE"),
@@ -85,6 +87,7 @@
 	}
 
 	// do executes a timed section of the stats command.
+
 	do := func(name string, f func() error) (time.Duration, error) {
 		start := time.Now()
 		fmt.Fprintf(os.Stderr, "%-30s", name+"...")
@@ -128,12 +131,12 @@
 
 	if _, err := do("Querying memstats", func() error {
 		memStats, err := executeCommand(ctx, cli.server, &protocol.Command{
-			Command: command.MemStats.String(),
+			Command: protocolcommand.MemStats.String(),
 		})
 		if err != nil {
 			return err
 		}
-		stats.MemStats = memStats.(command.MemStatsResult)
+		stats.MemStats = memStats.(protocolcommand.MemStatsResult)
 		return nil
 	}); err != nil {
 		return err
@@ -141,12 +144,12 @@
 
 	if _, err := do("Querying workspace stats", func() error {
 		wsStats, err := executeCommand(ctx, cli.server, &protocol.Command{
-			Command: command.WorkspaceStats.String(),
+			Command: protocolcommand.WorkspaceStats.String(),
 		})
 		if err != nil {
 			return err
 		}
-		stats.WorkspaceStats = wsStats.(command.WorkspaceStatsResult)
+		stats.WorkspaceStats = wsStats.(protocolcommand.WorkspaceStatsResult)
 		return nil
 	}); err != nil {
 		return err
@@ -192,13 +195,13 @@
 	return nil
 }
 
-// GoplsStats holds information extracted from a gopls session in the current
+// statsJSON holds information extracted from a gopls session in the current
 // workspace.
 //
 // Fields that should be printed with the -anon flag should be explicitly
 // marked as `anon:"ok"`. Only fields that cannot refer to user files or code
 // should be marked as such.
-type GoplsStats struct {
+type statsJSON struct {
 	GOOS, GOARCH                 string `anon:"ok"`
 	GOPLSCACHE                   string
 	GoVersion                    string `anon:"ok"`
@@ -207,9 +210,9 @@
 	InitialWorkspaceLoadDuration string `anon:"ok"` // in time.Duration string form
 	CacheDir                     string
 	BugReports                   []bug.Bug
-	MemStats                     command.MemStatsResult       `anon:"ok"`
-	WorkspaceStats               command.WorkspaceStatsResult `anon:"ok"`
-	DirStats                     dirStats                     `anon:"ok"`
+	MemStats                     protocolcommand.MemStatsResult       `anon:"ok"`
+	WorkspaceStats               protocolcommand.WorkspaceStatsResult `anon:"ok"`
+	DirStats                     dirStats                             `anon:"ok"`
 }
 
 type dirStats struct {
diff --git a/gopls/internal/cmd/subcommands.go b/gopls/internal/cmd/subcommands.go
index 22034b9..ee5eaad 100644
--- a/gopls/internal/cmd/subcommands.go
+++ b/gopls/internal/cmd/subcommands.go
@@ -9,13 +9,11 @@
 	"flag"
 	"fmt"
 	"text/tabwriter"
-
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // subcommands is a helper that may be embedded for commands that delegate to
 // subcommands.
-type subcommands []tool.Command
+type subcommands []command
 
 func (s subcommands) DetailedHelp(f *flag.FlagSet) {
 	w := tabwriter.NewWriter(f.Output(), 0, 0, 2, ' ', 0)
@@ -31,26 +29,26 @@
 
 func (s subcommands) Run(ctx context.Context, args ...string) error {
 	if len(args) == 0 {
-		return tool.CommandLineErrorf("must provide subcommand")
+		return commandLineErrorf("must provide subcommand")
 	}
 	command, args := args[0], args[1:]
 	for _, c := range s {
 		if c.Name() == command {
 			s := flag.NewFlagSet(c.Name(), flag.ExitOnError)
-			return tool.Run(ctx, s, c, args)
+			return runCommand(ctx, s, c, args)
 		}
 	}
-	return tool.CommandLineErrorf("unknown subcommand %v", command)
+	return commandLineErrorf("unknown subcommand %v", command)
 }
 
-func (s subcommands) Commands() []tool.Command { return s }
+func (s subcommands) Commands() []command { return s }
 
-// getSubcommands returns the subcommands of a given Command.
-func getSubcommands(a tool.Command) []tool.Command {
-	// This interface is satisfied both by tool.Commands
-	// that embed subcommands, and by *cmd.Application.
+// getSubcommands returns the subcommands of a given command.
+func getSubcommands(a command) []command {
+	// This interface is satisfied both by commands
+	// that embed subcommands, and by *cmd.application.
 	type hasCommands interface {
-		Commands() []tool.Command
+		Commands() []command
 	}
 	if sub, ok := a.(hasCommands); ok {
 		return sub.Commands()
diff --git a/gopls/internal/cmd/symbols.go b/gopls/internal/cmd/symbols.go
index 4641b6a..36fc208 100644
--- a/gopls/internal/cmd/symbols.go
+++ b/gopls/internal/cmd/symbols.go
@@ -12,12 +12,11 @@
 	"sort"
 
 	"golang.org/x/tools/gopls/internal/protocol"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // symbols implements the symbols verb for gopls
 type symbols struct {
-	app *Application
+	app *application
 }
 
 func (r *symbols) Name() string      { return "symbols" }
@@ -33,7 +32,7 @@
 }
 func (r *symbols) Run(ctx context.Context, args ...string) error {
 	if len(args) != 1 {
-		return tool.CommandLineErrorf("symbols expects 1 argument (position)")
+		return commandLineErrorf("symbols expects 1 argument (position)")
 	}
 
 	cli, _, err := r.app.connect(ctx)
diff --git a/gopls/internal/cmd/vulncheck.go b/gopls/internal/cmd/vulncheck.go
index 7babf0d..97d1aa2 100644
--- a/gopls/internal/cmd/vulncheck.go
+++ b/gopls/internal/cmd/vulncheck.go
@@ -16,7 +16,7 @@
 // vulncheck implements the vulncheck command.
 // TODO(hakim): hide from the public.
 type vulncheck struct {
-	app *Application
+	app *application
 }
 
 func (v *vulncheck) Name() string   { return "vulncheck" }
diff --git a/gopls/internal/cmd/workspace_symbol.go b/gopls/internal/cmd/workspace_symbol.go
index cd4ebc6..07cac18 100644
--- a/gopls/internal/cmd/workspace_symbol.go
+++ b/gopls/internal/cmd/workspace_symbol.go
@@ -12,14 +12,13 @@
 
 	"golang.org/x/tools/gopls/internal/protocol"
 	"golang.org/x/tools/gopls/internal/settings"
-	"golang.org/x/tools/gopls/internal/tool"
 )
 
 // workspaceSymbol implements the workspace_symbol verb for gopls.
 type workspaceSymbol struct {
 	Matcher string `flag:"matcher" help:"specifies the type of matcher: fuzzy, fastfuzzy, casesensitive, or caseinsensitive.\nThe default is caseinsensitive."`
 
-	app *Application
+	app *application
 }
 
 func (r *workspaceSymbol) Name() string      { return "workspace_symbol" }
@@ -39,7 +38,7 @@
 
 func (r *workspaceSymbol) Run(ctx context.Context, args ...string) error {
 	if len(args) != 1 {
-		return tool.CommandLineErrorf("workspace_symbol expects 1 argument")
+		return commandLineErrorf("workspace_symbol expects 1 argument")
 	}
 
 	opts := r.app.options
diff --git a/gopls/internal/test/integration/bench/bench_test.go b/gopls/internal/test/integration/bench/bench_test.go
index f42cf47..4286f48 100644
--- a/gopls/internal/test/integration/bench/bench_test.go
+++ b/gopls/internal/test/integration/bench/bench_test.go
@@ -24,7 +24,6 @@
 	"golang.org/x/tools/gopls/internal/protocol/command"
 	"golang.org/x/tools/gopls/internal/test/integration"
 	"golang.org/x/tools/gopls/internal/test/integration/fake"
-	"golang.org/x/tools/gopls/internal/tool"
 	"golang.org/x/tools/gopls/internal/util/bug"
 	"golang.org/x/tools/gopls/internal/util/fakenet"
 	"golang.org/x/tools/internal/event"
@@ -57,7 +56,7 @@
 func TestMain(m *testing.M) {
 	bug.PanicOnBugs = true
 	if os.Getenv(runAsGopls) == "true" {
-		tool.Main(context.Background(), cmd.New(), os.Args[1:])
+		cmd.Main()
 		os.Exit(0)
 	}
 	event.SetExporter(nil) // don't log to stderr
diff --git a/gopls/internal/test/integration/regtest.go b/gopls/internal/test/integration/regtest.go
index 25ea415..baea9df 100644
--- a/gopls/internal/test/integration/regtest.go
+++ b/gopls/internal/test/integration/regtest.go
@@ -5,7 +5,6 @@
 package integration
 
 import (
-	"context"
 	"flag"
 	"fmt"
 	"os"
@@ -18,7 +17,6 @@
 
 	"golang.org/x/tools/gopls/internal/cache"
 	"golang.org/x/tools/gopls/internal/cmd"
-	"golang.org/x/tools/gopls/internal/tool"
 	"golang.org/x/tools/gopls/internal/util/memoize"
 	"golang.org/x/tools/internal/drivertest"
 	"golang.org/x/tools/internal/gocommand"
@@ -144,7 +142,7 @@
 	// If this magic environment variable is set, run gopls instead of the test
 	// suite. See the documentation for runTestAsGoplsEnvvar for more details.
 	if os.Getenv(runTestAsGoplsEnvvar) == "true" {
-		tool.Main(context.Background(), cmd.New(), os.Args[1:])
+		cmd.Main()
 		return 0
 	}
 
diff --git a/gopls/main.go b/gopls/main.go
index 0a79d27..75e4ca2 100644
--- a/gopls/main.go
+++ b/gopls/main.go
@@ -11,15 +11,12 @@
 package main
 
 import (
-	"context"
 	"log"
-	"os"
 
 	"golang.org/x/telemetry"
 	"golang.org/x/telemetry/counter"
 	"golang.org/x/tools/gopls/internal/cmd"
 	"golang.org/x/tools/gopls/internal/filecache"
-	"golang.org/x/tools/gopls/internal/tool"
 	versionpkg "golang.org/x/tools/gopls/internal/version"
 )
 
@@ -51,6 +48,5 @@
 		log.Fatalf("gopls cannot access its persistent index (disk full?): %v", err)
 	}
 
-	ctx := context.Background()
-	tool.Main(ctx, cmd.New(), os.Args[1:])
+	cmd.Main()
 }