AI Agents & MCP¶
GTB enables your CLI applications to act as powerful autonomous agents through native support for the Model Context Protocol (MCP). Instead of just being a manual tool, your application can become a "brain extension" for an AI assistant.
The Model Context Protocol (MCP)¶
MCP is an open standard that allows AI models (like Claude or Gemini) to safely discover and interact with local tools. By implementing this protocol, GTB removes the need for custom integrations or wrapper scripts for every different AI service.
How it Works¶
When you build a CLI with GTB, the framework automatically maps your Cobra command tree to a set of MCP Tool Definitions:
- Command Name -> Tool Name
- Short Description -> Tool Description
- Flags & Arguments -> JSON Schema Parameters
Exposing your Tool¶
Every GTB application includes a built-in mcp command group. mcp start runs a
JSON-RPC server over standard I/O, the default transport for editor and desktop
integrations such as Claude Desktop:
For networked clients, mcp stream serves the same tools over a streamable-HTTP
endpoint (configurable via --host and --port):
Integration with AI Assistants¶
To use your tool as an agent, you simply configure your preferred AI client (like Claude Desktop) to run your tool in MCP mode. The assistant will:
- Call
mytool mcp starton startup. - Discover all available commands as tools.
- Contextually call your commands when a user's prompt requires it.
Why use MCP?¶
- Universal Compatibility: Write once, and your tool works across any AI assistant that supports the protocol.
- Zero Effort: No extra code required. If it's a command in your CLI, it's a tool for the agent.
- Safety & Control: The AI is restricted to the specific commands and parameters you've defined in your tool's manifest.
Tip
To see how to configure specific AI clients (like Cursor or Claude Desktop) to use your tool as an agent, refer to the MCP CLI Guide.
Gating sensitive commands¶
By default every runnable command is exposed as an MCP tool. For tools with
commands that publish, spend irreversibly, or touch secrets (post, approve,
auth, deploy, …), you usually want those off the MCP surface so an
assistant cannot invoke them unprompted, while keeping them fully usable on the
CLI. GTB provides per-command, build-time exposure control for exactly this.
The model¶
- Exposed by default. A command with no decision is an MCP tool, exactly as before. Tools that gate nothing behave identically to earlier versions.
- Explicitly excluded per command. The decision is a property of the
command's own manifest entry (
mcp_enabled), tri-state:true= exposed,false= excluded, absent = inherit. - Subtree inheritance with override. Excluding a parent withholds its whole subtree; a descendant may set itself back to exposed to override an excluded ancestor (and that descendant's own subtree follows it, until some deeper command excludes again). Resolution takes the nearest explicit decision walking up the command tree, defaulting to exposed.
- Build-time only: no runtime toggle. Exposure is baked into the binary as
a command annotation; no config file, env var, or flag can re-expose an
excluded command at runtime. The MCP tool surface is therefore fixed and
auditable in the shipped binary, a deliberate security property, not an
oversight. Changing it requires re-generating from an updated manifest (or the
gtb enable/disable mcpverbs below) and shipping a new build.
Turning exposure on and off¶
For a tool built with GTB's generator, use the framework verbs (they update
.gtb/manifest.yaml and re-render the affected command's cmd.go). gtb
enable/disable mcp is dual-purpose, with no argument it toggles the
mcp feature (the MCP server subsystem); with one or more command paths it
gates those commands' exposure:
# Withhold a command (and its subtree) from the MCP tool surface
gtb disable mcp post
# Put a command back on the surface (records an explicit, auditable re-enable)
gtb enable mcp post
# Gate several at once (each is a command path)
gtb disable mcp post approve auth
# No argument → toggles the mcp FEATURE on/off (not per-command exposure)
gtb disable mcp
Or decide at scaffold time:
# Generate a command kept off the MCP surface (still runnable on the CLI)
gtb generate command -n post --mcp-enabled=false
The interactive gtb generate command wizard asks "Expose to MCP?" as a
dedicated step (defaulting to expose), so the decision is a conscious one. On
an existing project, gtb wizard lists every
command on its MCP page with its current exposure ticked; each tick you change
is the same enable mcp/disable mcp call.
A protected command is refused by enable/disable mcp, unprotect it first.
How it works¶
GTB stamps a setup.ExcludeFromMCP(cmd) or setup.IncludeInMCP(cmd) marker
into the generated command constructor (pkg/setup, MCPExposure enum), and
hands setup.IsExposedToMCP (the nearest-ancestor walk) to the go/mcp Cobra
binding as its exposure policy, so a command resolving to excluded is never
bound. The marker round-trips through both regenerate project (manifest → code)
and regenerate manifest (code → manifest), so the gating is never silently lost.
See the generated command exposure
spec for the
full design, and setup.IsExposedToMCP / setup.ExcludeFromMCP for the API.
Telling a client what a command does¶
Exposure says whether a command is a tool. MCP's tool annotations say what
kind of tool it is: a display title and four behavioural hints,
readOnlyHint, destructiveHint, idempotentHint and openWorldHint. A
client uses them to decide what to surface, what to confirm and what to call
freely, without reading the description prose. They are hints, not permissions:
a client may act on them, and nothing in GTB grants access because of one.
The model¶
- Per command, tri-state, no inheritance. Each hint is
true,falseor unset. Unset writes no key, so a client sees only what was actually declared. A hint describes one executable command; a group states nothing about its children, unlike exposure. - Recorded in the manifest, rendered into the code. The command's manifest
entry carries an
mcp_hintsblock, and itscmd.gocarries onesetup.AnnotateMCP(cmd, setup.MCPHints{...})call after the exposure marker.regenerate projectandregenerate manifestround-trip it in both directions. - Additive.
setup.AnnotateMCPadds its keys tocmd.Annotationsbeside GTB's feature and exposure keys; it never replaces the map. - The keys are ophis's spellings (
readOnlyHintand friends). A tool that set them by hand under the previous library keeps working, and the go/mcp Cobra binding reads the same keys.
Built-in defaults¶
Every command the framework ships declares its own hints, so a tool built on GTB gets them without saying anything:
| Commands | read-only | destructive | idempotent | open-world |
|---|---|---|---|---|
version, docs, changelog, doctor, config get, config list, config validate, telemetry status |
true | false | true | false |
config set, config unset, telemetry enable, telemetry disable |
false | false | true | false |
update, init and its subcommands |
false | false | false | true |
config migrate-credentials |
false | true | false | false |
The four presets behind that table are setup.MCPReadOnly(),
setup.MCPLocalWrite(), setup.MCPOpenWorld() and setup.MCPDestructive(); a
hand-written command can apply one the same way:
Setting them on a generated command¶
gtb annotate report --read-only --title "Spend report"
gtb annotate generate --open-world --idempotent=false
gtb annotate generate --clear
See the annotate reference and spec 0201, which answers issue #36.
Common Use Cases¶
- Integrating your CLI with AI coding assistants (e.g., Cursor, Windsurf).
- Enabling autonomous agents to perform infrastructure or DevOps tasks.
- Providing a standard interface for AI-to-tool communication.
Implementation¶
The MCP command is the estate's own go/mcp
module, wired through pkg/mcp (spec 0201 D1). pkg/mcp is also the link
that gives a tool the feature (spec 0202): a blank import of it from the tool's
main declares mcp as a link kind and contributes the command to the root
through setup.SlotRootCommand, and nothing else in the framework imports
go/mcp. pkg/mcp.NewCmdMCP builds the mcp tree with GTB's conventions
applied once, so a tool built on GTB gets them without saying anything:
- Exposure comes from the tree's own markers (
setup.IsExposedToMCP), and a pure command group (setup.GroupRunE) is never published: a tool that prints usage is noise to a client. - The global flags are withheld.
--config,--debug,--ciand--accessiblesteer the process, not the command, and--configwould let a client point the tool at another configuration file.--outputstays, because a client wants JSON. - Operations are grouped by feature. The
setup.Wrapfeature ID becomes the operation's group, sosearch_toolsnarrowed byconfigortelemetrymeans the same thing in every GTB tool. - The publication mode comes from
props.Tool.MCP, rendered fromproperties.mcp.modein the manifest. The binary never reads the manifest. - Logs go to stderr at the level the root's
--debugand config reload already move (Props.LogLevel); stdout carries the protocol, so the command is stampedsetup.MarkProtocolStdoutand the root renders no prompt under it.
A tool without MCP¶
The feature is the file. A generated project carries cmd/<name>/mcp.go, one
blank import of pkg/mcp, and the manifest's mcp entry decides whether it
exists: the feature defaults on, so a manifest that says nothing links it, and
gtb disable mcp (or mcp: false) removes the file. A binary built without the
import has no mcp command, no mcp feature, and neither
gitlab.com/phpboyscout/go/mcp nor the MCP Go SDK in its build:
prints nothing for such a build, and an SBOM taken from the binary agrees. One
caveat: the ai feature's chat providers link go/chat-mcptools, which
imports the same SDK for the chat side's MCP tool bridge, so a tool with a
chat provider still carries the SDK (not go/mcp) through that module. A
hand-wired tool adds or omits the import itself; the
migration note covers a tool
that predates the link.
Compact and direct publication¶
By default a client sees three tools, whatever the size of the command
tree: search_tools, get_tool_details and call_tool. The model searches or
browses, inspects the schema of the one it needs, and calls it. This is
progressive discovery: the catalogue is not pushed into every context window,
and a tool with 78 commands costs a client the same as one with three.
The trade is native per-tool approval. A client sees call_tool as the tool,
so its annotations are conservative (destructive, open-world), and the
per-command hints from annotate reach the model through get_tool_details
rather than the client's approval UI. A tool that wants one native tool per
command, with each tool's own annotations in the client's UI, sets the mode:
That records properties.mcp.mode: direct, renders MCP:
props.MCPConfig{Mode: props.MCPDirect} into the generated root, and takes
effect on the next build. gtb set mcp.mode compact returns to the default.
The mode is project-level publication configuration: gtb disable mcp still
wins, and per-command mcp_enabled still gates exposure.
Execution¶
Every call runs the command as a subprocess of the same binary, with the
host's environment and working directory, stdin closed, a five-minute timeout
and one MiB of retained output. One command runs at a time; a second call gets
a retryable busy failure while search and inspection stay available. A
cancelled call stops the command and everything it started (a process group on
Unix, a job object on Windows). Non-zero exit is a command_failed result that
still carries stdout, stderr and the exit code.
mytool mcp tools writes the full authorised catalogue to mcp-tools.json
regardless of mode: it is the offline view of what a client could discover.
For the module's own contracts see mcp.go.phpboyscout.uk and go/mcp spec 0001; for GTB's side, spec 0201.