How to Integrate Go CLIs in 2026
Most Go CLIs that need to integrate with other tools face the same temptation: invent a framework, then spend the next year maintaining it.
Usually the right starting point is simpler. Use Unix process composition first. Add richer protocol layers only when they solve a real problem.
This matters more in 2026 because the integration surface has grown. A modern CLI may need to work with:
- other command-line tools
- LLM-backed inference tools
- MCP servers
- local and remote plugins
- provenance and policy systems
Those are real needs, but they do not justify collapsing everything into one runtime.
The baseline: executables, pipes, JSON
The most robust integration model is still the old one:
- discover an executable
- invoke it as a child process
- pass text or JSON over stdin/stdout
- treat stderr as diagnostics
- keep flags and exit codes conventional
For a Go CLI named kg, a plugin named kg-nlm is a better integration point than a shared object ABI, embedded scripting runtime, or custom daemon.
That gives you:
- language independence
- shell composability
- easy debugging
- easy packaging
- stable failure boundaries
It also keeps upgrade blast radius small. If the downstream tool changes, the adapter changes. The host CLI does not need to absorb that churn.
A concrete rule
If a user can reasonably type:
tool foo ...
then your first design should probably be:
tool-foo ...
with the host CLI discovering and dispatching to it.
That design is boring, which is a point in its favor.
Where richer systems fit
There are three common reasons teams move beyond plain executables.
1. Typed discovery
You want to know what a plugin is, what it does, and how to render it in help or UIs.
That does not require abandoning the Unix process model. A small metadata probe is enough:
tool-foo --tool-plugin-metadata
returning JSON like:
{
"name": "foo",
"summary": "Short description"
}
This is cheap and good enough for plugin list and plugin info.
2. Structured tool invocation
This is where MCP helps. MCP is useful when the integration is no longer just “run this subcommand” but “expose a tool surface to an agent or host runtime.”
Use MCP when:
- a client needs typed tools rather than shell commands
- the integration is agent-facing
- the host needs capability negotiation
- the integration is multi-step and stateful
Do not use MCP by default when a plain CLI adapter is enough. A one-shot shell command is easier to install, reason about, audit, and support.
The useful rule is:
- CLI first for people and scripts
- MCP second for agents and rich hosts
These can coexist as separate binaries.
Inference changes the architecture
Inference is the main reason older plugin advice breaks down.
A CLI that talks to models is no longer just performing deterministic local computation. It crosses privacy, cost, latency, and provenance boundaries.
That means you should separate:
- local deterministic selection
- remote or probabilistic inference
Do not send an entire working set to a model because it is convenient. Reduce first.
The host CLI or adapter should:
- resolve the local target
- gather only relevant context
- trim and normalize that context
- invoke inference
This is good systems design, not just cost control. It reduces privacy exposure and makes results easier to explain.
Privacy defaults matter
Most CLI integrations have terrible privacy defaults because they start from os.Environ(), broad filesystem access, and convenience logging.
That is lazy design.
If your CLI handles notes, source code, prompts, transcripts, credentials, or production state, default to least disclosure.
Reasonable defaults:
- pass an allowlisted environment, not the whole parent environment
- send selected files, not whole repositories or vaults
- keep stderr for diagnostics and stdout for intended output
- avoid logging raw content unless debug mode explicitly enables it
- make network use explicit in docs and manifests
A plugin on PATH is local code execution. Treat it that way.
Security lessons from a real plugin system
The it2 CLI has already explored a more ambitious plugin model: executable discovery, typed plugin classes, manifests, trust levels, caching, and sandbox design.
That work is useful because it exposes the real failure modes.
The most important ones are ordinary:
Environment leaks
Passing the full parent environment to plugins leaks secrets. This is one of the easiest mistakes to make and one of the highest value fixes.
Mixed stdout and stderr
If you expect machine-readable output, do not merge stderr into stdout. Warnings should not corrupt the data channel.
Missing process-tree cleanup
Timeouts that only kill the top-level plugin process are incomplete. Shell wrappers often spawn children. Clean up the process group.
Discovery confused with trust
Finding an executable on PATH tells you nothing about whether you should trust it.
Unbounded inputs
Do not shovel whole buffers, large repos, or arbitrary terminal transcripts into every plugin invocation. Bound the input contract.
These are not exotic plugin problems. They are systems hygiene problems.
Provenance without turning your CLI into a package manager
Supply chain security is real, but many CLI designs overreact by inventing an entire plugin marketplace before they have a stable interface.
A better progression is:
Layer 0: plain executable discovery
Support tool-foo on PATH. This is the compatibility layer and the local development path.
Layer 1: optional metadata
Add a small metadata probe for help text and UI rendering.
Layer 2: optional manifest
If a plugin wants higher trust, let it ship a manifest that declares:
- name and version
- homepage and source repo
- source commit
- binary checksum
- intended capabilities
- network intent
- write intent
This is not a sandbox. It is a statement that can be checked and shown to users.
Layer 3: optional verification
For distributed plugins, add:
- signed releases
- checksums
- provenance attestations
- reproducible build instructions
- binary scanning for Go executables, for example
govulncheck -mode binary /path/to/tool-foo
Layer 4: local policy
Let users or orgs enforce policy:
- deny plugins with network access
- warn on checksum changes
- require provenance for auto-installed plugins
- require explicit approval before write-capable plugins run
This separates compatibility from trust. That separation is important.
For Go-native plugin ecosystems, govulncheck -mode binary is a useful inline
verification step for released executables. It is a good fit for:
- CI on produced binaries
- install-time checks
- local approval flows when a plugin binary changes
It is not a substitute for signatures, checksums, or provenance. It answers a
different question: whether the built Go executable appears to contain known
reachable vulnerabilities. It does not prove who built it, whether it is the
expected file, or whether a shell wrapper around it is safe.
macOS sandboxing and XPC
macOS adds another temptation: once a CLI starts dealing with inference,
credentials, or enterprise policy, it becomes attractive to move everything
behind a service boundary.
That instinct is sometimes right, but not as a default.
The App Sandbox is usually the wrong primitive for a CLI
The macOS App Sandbox works best for GUI apps with well-defined entitlement
needs and brokered user file selection.
It is awkward for developer CLIs because:
- CLIs need arbitrary filesystem access
- CLIs often need subprocesses
- CLIs are expected to compose with shells, pipes, CI, and ssh
- notes, repos, and worktrees rarely live in sandbox-friendly locations
For that reason, most Go CLIs should not try to make the main executable an
App Sandbox target.
XPC is better when you need a broker
If you need stronger boundaries on macOS, XPC is more useful than trying to
contort the whole CLI into a sandboxed app.
An XPC service can own:
- credentials
- network policy
- attestation checks
- audit logging
- narrowly scoped filesystem access
The CLI remains the interactive surface. The service becomes the trusted
broker.
A good split
The clean macOS shape is often:
- CLI for user interaction and local reduction
- child-process plugins for ordinary composition
- optional XPC service for sensitive or policy-governed operations
That preserves Unix ergonomics without giving up a higher-assurance path.
When XPC is justified
Use an XPC service when:
- credentials must not leak into plugin environments
- network use needs policy enforcement
- operations should be auditable at a service boundary
- an enterprise deployment wants a managed local trust anchor
- background inference or indexing should outlive one shell command
When it is not
Do not require XPC when:
- the integration is just command translation
- the tool should remain easy to script
- the tool should work unchanged in CI or over ssh
- the extra lifecycle and packaging complexity outweighs the benefit
This is the same general lesson as with MCP: do not force the richer system
into the default path unless it solves a concrete problem the simpler system
cannot.
Security consequence
A useful rule is:
- child process for compatibility
- XPC for brokering
If a plugin needs secrets, attested network access, or strong local policy,
that functionality probably belongs in an XPC broker or helper service, not in
an arbitrary executable discovered from PATH.
The design mistake to avoid
The common failure mode is trying to solve all trust and orchestration concerns inside the host CLI itself.
That usually leads to:
- a daemon
- a registry
- a sandbox layer
- a policy engine
- an installer
- a verifier
- a transport protocol
- three kinds of cache
Sometimes those are justified. Often they are not.
Before you add them, ask:
- Is the plain executable model insufficient?
- Is this needed by users, or only attractive to implementers?
- Can this be a separate tool instead of host CLI complexity?
If the answer is not clear, defer it.
A practical recommendation for 2026
For most Go CLIs, the right default architecture is:
- External command plugins by executable name
- Text and JSON over stdio
- Explicit metadata probe
- Strict environment allowlist
- Deadlines and process-group cleanup
- Optional manifests for provenance and policy
- Separate MCP servers for agent-facing integrations
That stack handles most real-world integration needs without baking in irreversible complexity.
The point is not to avoid sophistication forever. The point is to earn it.
Unix got the first part right decades ago: processes, pipes, and composability are a very strong substrate.
In 2026, the best CLI designs still start there.