Skip to content

v0.x: the mcp feature is a link, and a tool without it ships without go/mcp

Spec 0202, the extraction spec 0201's OQ1 deferred.

What changed

pkg/cmd/root no longer registers the mcp command. The mcp feature is a link kind, like keychain: pkg/mcp declares it and contributes the command to the root when a main blank-imports the package, and nothing else in the framework imports go/mcp. A binary without the import has no mcp command, no mcp feature, and neither gitlab.com/phpboyscout/go/mcp nor github.com/modelcontextprotocol/go-sdk in its build (go version -m on the binary shows the difference). A tool that links a chat provider still carries the SDK through go/chat-mcptools, the chat side's tool bridge; that is the chat module's, not the mcp link's.

The feature defaults on. A manifest that says nothing about mcp still means MCP is on, as it did before; mcp: false now removes the import rather than rendering props.Disable(props.McpCmd).

Two smaller changes to the framework's API:

  • Props.LogLevel (read through GetLogLevel) is the *slog.LevelVar the root moves for --debug and the config's log.level. The root used to keep it privately and pass it to the MCP constructor.
  • setup.SlotRootCommand, setup.RootCommandProvider and setup.RegisterRootCommands let any feature hand the root a top-level command; setup.MarkProtocolStdout marks a command whose stdout carries a wire protocol, which the root uses to withhold interactive prompts. The root's private feature match for the mcp subtree is gone.

What you need to do

A generated project: run gtb regenerate project. It writes cmd/<name>/mcp.go for a project whose manifest does not disable mcp, and stops rendering the Disable for one that does. Until you regenerate, the tool builds but has no mcp command.

A hand-wired tool: add the blank import beside your main:

import _ "gitlab.com/phpboyscout/go-tool-base/pkg/mcp"

Omit it deliberately for a build that must carry no MCP surface.

A tool that enables the feature explicitly (props.Enable(props.McpCmd)) must link it too: enabling a feature the binary did not declare is a construction error (spec 0199 OQ2). Disabling one it did not declare is ignored, so an existing props.Disable(props.McpCmd) keeps compiling and doctor lists it as ignored.

A host that called mcp.NewCmdMCP itself is unchanged: the constructor and its options keep their signatures. Pass p.GetLogLevel() where you passed the root's level before.

Verifying a build without MCP

go version -m ./bin/<tool> | grep -E 'go/mcp|modelcontextprotocol'

prints nothing for a binary built without the import.