A local/private control plane that turns trusted CLI plugins into managed capabilities, with local stdio MCP exposure.
alter is not a business-logic tool. It discovers and inspects plugin adapters. Actual upstream tools do not need to know anything about alter, MCP, manifests, or schemas. Adapter plugins translate those tools into the alter contract.
No daemon runs. MCP mode is alter mcp over stdio.
alter: Cobra CLI entrypoint, plugin discovery, inspection, runtime discovery, adapter invocation, and MCP stdio servinginternal/runtime: runtime discovery and execution boundaryinternal/plugin: typed plugin manifest parsing, discovery, inspection, and static layout checksinternal/adapter: adapter execution and output normalizationinternal/mcp: MCP server setup, tool registration, and adapter-backed tool callsmise: plugin-local runtime manageralter-foo: adapter owned byplugins/foofoo: actual external tool wrapped by adapter
Plugin path is the local command name:
plugins/hello
plugins/test-runtime
plugins/foo
plugins/bar
Ownership and upstream information belong in alter.plugin.toml, not in filesystem path.
Each plugin directory contains alter.plugin.toml:
[plugin]
name = "hello"
description = "Example alter plugin"
maintainer = "iomz"
entrypoint = "alter-hello"
[upstream]
name = "hello"
repository = ""
[runtime]
manager = "mise"
[mcp]
enabled = true
namespace = "hello"Manifests are parsed with pelletier/go-toml into strongly typed structures. Required
fields are:
plugin.nameplugin.descriptionplugin.entrypointruntime.manager
plugin.name must match the directory name. runtime.manager is currently mise.
Prototype plugin directories:
plugins/hello/
alter.plugin.toml
alter.mise.toml
alter-hello
plugins/test-runtime/
alter.plugin.toml
alter.mise.toml
alter-test-runtime
test-runtime is a dedicated mise isolation proof plugin. It explicitly declares
only Node.js in alter.mise.toml:
[tools]
node = "24"It exists to verify that mise mode installs only plugin-declared runtimes and ignores
user/global mise or asdf state before real plugins such as ingest are wired in.
Executable adapters expose three commands:
<entrypoint> manifest
<entrypoint> doctor
<entrypoint> invoke <json>
invoke receives a JSON envelope:
{
"tool": "greet",
"args": {
"name": "iomz"
}
}Adapters return JSON. alter validates and pretty-prints adapter JSON before writing it to stdout.
Foundation-only plugin directories may contain only a manifest:
plugins/foo/
alter.plugin.toml
plugins/bar/
alter.plugin.toml
go run ./cmd/alter setup mise
go run ./cmd/alter setup shell
go run ./cmd/alter setup cleanup
go run ./cmd/alter plugin list
go run ./cmd/alter plugin inspect hello
go run ./cmd/alter plugin inspect hello --json
go run ./cmd/alter plugin doctor hello
go run ./cmd/alter plugin doctor test-runtime
go run ./cmd/alter plugin trust-status test-runtime
go run ./cmd/alter plugin trust test-runtime
go run ./cmd/alter hello greet --name iomz
ALTER_LOG=debug go run ./cmd/alter test-runtime node-version
go run ./cmd/alter plugin untrust test-runtime
go run ./cmd/alter mcpHuman-facing commands use structured sections, compact tables, and semantic status
styles. alter plugin inspect <name> defaults to a readable manifest summary; use
--json for raw parseable manifest output.
alter plugin doctor <name> performs static layout checks first. If an adapter entrypoint
exists, it prints runtime isolation diagnostics. Manifest-only plugin directories report
missing entrypoints as warnings.
alter does not modify global shell config and does not require mise shell activation.
Runtime discovery is handled through a MiseResolver abstraction. It returns absolute
paths only and checks:
miseonPATH~/.local/share/alter/bin/mise~/.local/bin/mise
If mise is missing, alter setup mise explains the bootstrap plan and asks for
confirmation before installing anything.
For plugin execution, alter first chooses a runtime mode:
direct: run the adapter entrypoint directly from the plugin workspacemise: run through mise only when plugin runtime config declares tools
Direct mode is the default when alter.mise.toml is missing or has no [tools]
entries and alter.tool-versions is missing or empty. In direct mode, alter does
not call mise install and does not call mise exec.
In mise mode, alter:
- discovers mise through the resolver
- runs
mise installinside the plugin workspace - runs
mise exec -- <entrypoint> ...inside the plugin workspace - validates adapter JSON output
- uses full paths internally
- shows an actionable error if
miseis missing
Plugin runtime execution is isolated from user global mise/asdf configuration. alter sets:
MISE_OVERRIDE_CONFIG_FILENAMES=alter.mise.toml
MISE_OVERRIDE_TOOL_VERSIONS_FILENAME=alter.tool-versions
MISE_OVERRIDE_TOOL_VERSIONS_FILENAMES=alter.tool-versions
MISE_LEGACY_VERSION_FILE=false
MISE_ASDF_COMPAT=false
MISE_GLOBAL_CONFIG_FILE=~/.local/state/alter/mise/config.toml
MISE_DATA_DIR=~/.local/state/alter/mise/data
MISE_CACHE_DIR=~/.cache/alter/mise
MISE_STATE_DIR=~/.local/state/alter/mise/state
Plugin runtime config lives in alter.mise.toml, not mise.toml. If a
tool-versions style file is needed, it must be named alter.tool-versions. This
prevents user files such as ~/.tool-versions, parent-directory .tool-versions,
or ~/.config/mise/config.toml from influencing alter-managed plugin execution.
Both the singular and plural tool-versions override environment names are set because
current mise settings expose the plural form while the alter policy names the singular
form.
The environment passed to mise starts from a small allowlist (HOME, PATH, TMPDIR,
TERM, LANG, LC_ALL) and does not inherit mise/asdf activation variables.
Before mise install, alter reads only the plugin workspace alter.mise.toml and
alter.tool-versions. If neither declares tools, install is skipped and the adapter
runs in direct mode. The hello plugin currently declares no mise-managed tools, so
invoking hello_greet must not install unrelated global tools such as lua, node,
python, ruby, go, pnpm, or poetry.
The test-runtime plugin declares exactly one mise-managed tool, node@24, so its
runtime mode is mise. Running alter test-runtime node-version should install or
reuse only that declared Node.js runtime, then execute the adapter through mise exec
inside plugins/test-runtime.
Mise-managed plugins require explicit trust before execution. Trust is recorded in:
~/.local/state/alter/trust/plugins.json
The trust store fingerprints:
alter.plugin.tomlalter.mise.toml, when presentalter.tool-versions, when present- adapter entrypoint file, when it exists inside the plugin workspace
Trust is invalidated when any trusted fingerprint changes, or when the plugin workspace path changes. The next run refuses to execute and tells you to review and trust again.
Use:
alter plugin trust-status test-runtime
alter plugin trust test-runtime
alter plugin untrust test-runtimealter plugin trust <name> shows a review summary and asks for confirmation with huh.
Trust is never written silently. The review means:
alter.mise.tomlis plugin-owned runtime policy. It declares tools mise may install or reuse for this plugin.- Trusting it means accepting this local plugin directory, its runtime config, and its adapter entrypoint as code you are willing to run.
- Running untrusted code means mise may download tool archives and the adapter process may execute local commands with your user permissions.
- To trust it, inspect
alter.plugin.toml,alter.mise.toml, and the adapter entrypoint; confirm declared tools and adapter code match your expectation; then run the command again. If it does not match, do not run that plugin.
Direct runtime plugins with no declared tools do not require trust. MCP mode cannot prompt for trust, so it fails with a concise actionable error when a tool needs trust.
Set ALTER_LOG=debug to print runtime decision details to stderr. Debug output includes
plugin name, workspace, adapter entrypoint, runtime mode, runtime config presence,
declared tools, install skip status, mise path when used, mise cwd, sanitized mise
environment values, and exact commands. Debug logging does not print arbitrary inherited
environment variables.
Prototype intentionally does not auto-trust arbitrary mise configs silently.
The alter core owns plugin discovery, manifest parsing, runtime discovery, runtime wrapping, and adapter output normalization. Adapter plugins own translation into upstream tools. Upstream tools do not implement alter-specific interfaces.
Execution flow:
alter
-> plugin adapter contract
-> runtime wrapper
-> output normalization
Adapter internals may call upstream tools. That call remains adapter-owned.
Automatic MCP tool generation from adapter metadata is future work. Current MCP registration is explicit and should remain outside manifest parsing and static discovery logic.
alter mcp serves MCP over stdio using modelcontextprotocol/go-sdk.
alter mcp is local stdio MCP exposure for alter-managed capabilities. It is part of
alter because it is the local AI-consumer boundary for trusted plugin adapters.
Remote delivery is a separate concern. HTTP bridges, reverse proxies, OAuth/OIDC gateways, hosted app platforms, and ChatGPT Custom App adapters are not alter's core responsibility. Prefer composing with existing implementations for those layers when possible.
Current exposed tool:
hello_greet
test_runtime_node_version
Tool registration is intentionally explicit. The current path is:
plugin metadata
-> tool registration
-> MCP exposure
-> adapter invocation
hello_greet calls the hello adapter's greet tool and test_runtime_node_version
calls the test-runtime adapter's node-version tool. Both return adapter JSON as text
content and structured content.
Future direction:
- derive MCP tools from adapter metadata
- expose more plugin tools after adapter metadata stabilizes
- keep MCP registration separate from plugin manifest parsing
- keep transport-specific code thin
- keep remote bridge, proxy, gateway, and auth concerns outside core alter unless a clear need emerges
Manual isolation check:
./bin/alter plugin doctor hello
./bin/alter plugin doctor test-runtime
./bin/alter plugin trust test-runtime
ALTER_LOG=debug ./bin/alter test-runtime node-version
npx -y @modelcontextprotocol/inspector ./bin/alter mcpIn the Inspector, invoking hello_greet should not install unrelated tools from
~/.tool-versions, ~/.config/mise/config.toml, parent-directory mise files, or shell
activation state.
Expected hello doctor output includes:
runtime mode: direct
mise install: skipped
declared tools: none
Expected test-runtime doctor output includes:
runtime mode: mise
mise install: required
declared tools: node@24
user/global mise/asdf config: ignored
Invoking test_runtime_node_version in MCP Inspector should return a Node.js version
and must not install Python, Ruby, Go, Lua, pnpm, Poetry, or anything from user/global
mise or asdf config.
alter setup mise checks PATH first, then alter-managed locations. If mise is
still unavailable, it shows an interactive confirmation prompt before bootstrap.
When confirmed, alter:
- downloads the official installer from
https://mise.run - runs it with
MISE_INSTALL_PATH=~/.local/share/alter/bin/mise - captures installer stdout/stderr
- shows alter-owned success output
- prints raw installer output only if installation fails
- verifies the installed binary is executable
- uses the full absolute path internally
alter setup mise never:
- modifies shell startup files
- runs
sudo - installs without confirmation
- configures future shell activation
alter setup cleanup removes only alter-managed mise runtime files:
~/.local/share/alter/bin/mise~/.local/state/alter/mise~/.cache/alter/mise
It never removes shell startup files, ~/.tool-versions, user global mise config, or asdf
files.
alter setup shell is a styled stub. Shell integration remains optional and explicit;
alter does not modify shell startup files.
Routine mechanical tasks are available through just:
just fmt
just lint
just test
just check
just doctor
just diffRecipes remain thin wrappers around Go, mise, and Git commands.
just check formats Go sources, runs go vet ./..., then runs go test ./....
Terminal output uses Charmbracelet libraries:
lipglossfor styled labelsglamourfor Markdown-rendered setup noteshuhas the prompt styling foundation for future interactive setup