Fire UserPromptSubmit in Run() before queuing, PreToolUse/PostToolUse in
executeStep() callbacks, and Stop hook in runPrompt()/RunOnce() after each
step. Emit HookBlockedEvent when a hook blocks a prompt or tool call.
SetupCLI() and its associated debug/CLI display logic now only run when
--prompt is set (non-interactive mode). Interactive mode uses the Bubble
Tea AppModel exclusively and never allocates a legacy CLI.
- Move SetupCLI call inside promptFlag != "" guard
- Remove cli parameter from runInteractiveModeBubbleTea (was unused _)
- Remove cli parameter from runNonInteractiveModeApp (no longer needed)
- Session history display remains guarded by cli != nil, so it correctly
skips in interactive mode
Replace the legacy runNonInteractiveMode path (which used runAgenticLoop
and the old CLI spinner/streaming display) with runNonInteractiveModeApp,
which delegates directly to appInstance.RunOnce(ctx, prompt, os.Stdout).
RunOnce never creates a tea.Program, so no intermediate spinner or
tool-call output is produced — satisfying both the normal and --quiet
non-interactive cases with the same codepath.
When --no-exit is set, runNonInteractiveModeApp hands off to the
interactive BubbleTea TUI via runInteractiveModeBubbleTea after the
single step completes.
Replace the old SetupCLI/runInteractiveLoop flow with a single
tea.NewProgram(AppModel) + appInstance.SetProgram() call. NewAppModel
now constructs InputComponent and StreamComponent inline so the parent
model is fully wired on construction.
TAS-18: Implements internal/app/app.go with the App orchestrator that satisfies
the ui.AppController interface. Wires all 7 agent callbacks to program.Send()
events, handles interactive approval via ToolApprovalNeededEvent channel, and
drains the prompt queue sequentially in a single background goroutine.
Refactors runNormalMode() in cmd/root.go to construct app.New() after session
messages are loaded, defer appInstance.Close(), and wire ToolApprovalFunc per
mode (AutoApproveFunc for non-interactive, nil for interactive TUI path).
Wraps []fantasy.Message with Add, Replace, GetAll, Clear, and Len methods.
Bridges to session.Manager for best-effort on-disk persistence on every mutation.
Refactors tool approval from the standalone ToolApprovalInput (which called
tea.Quit) into ApprovalComponent that returns approvalResultMsg{Approved: bool}
via a tea.Cmd, letting AppModel own the program lifecycle. Wires the
ToolApprovalNeededEvent handler in model.go to construct and display the
component.
Implements TAS-16: refactors the stream display into a self-contained
StreamComponent with a phase state machine (idle/spinner/streaming).
Reuses knightRiderFrames() for the KITT-style spinner, accumulates
streaming chunks, and renders tool call/result events inline.
Reset() clears all state between agent steps.
Replaces the standalone SlashCommandInput quit-on-submit pattern with a
proper child component (InputComponent) that returns submitMsg as a tea.Cmd.
Slash commands are executed against AppController (/clear, /clear-queue) or
return tea.Quit (/quit) — no os.Exit(). Also registers /clear-queue in the
slash command registry.
Handle /quit slash command (and aliases /q, /exit) in submitMsg handler by
returning tea.Quit before forwarding to the app layer. Add TODO comment in
cmd/root.go marking where defer appInstance.Close() belongs once app.App
exists (TAS-18).
Render step errors above the BT region via tea.Println() using the
existing RenderErrorMessage() renderers, mirroring the StepCompleteEvent
pattern. Reset stream state and transition back to stateInput on error.
Introduce the root Bubble Tea model (AppModel) and the three TUI-internal
message types needed by the unified architecture spec (TAS-10).
- internal/ui/events.go: submitMsg, approvalResultMsg, cancelTimerExpiredMsg
- internal/ui/model.go: AppModel with full stateInput/stateWorking/stateApproval
state machine, AppController interface for dependency inversion, child
component interfaces as stubs (TAS-15/16/17), double-tap ESC cancel with 2s
timer, routing of all 13 app-layer events, tea.Println on StepCompleteEvent,
stacked View() with separator and queue badge, WindowSizeMsg propagation.
Define all 13 event structs in internal/app/events.go that the app layer
sends to the TUI via program.Send(), including streaming, tool call, approval,
spinner, queue, and error events.
Replace micro-program pattern (3 separate tea.NewProgram calls) with a
single persistent program using child model composition. Introduces thick
app layer for agent orchestration, message queueing, and double-tap ESC
cancellation.
Drop the points spinner and message label in favor of a Knight Rider
scanner that bounces a red glow across 8 small squares. Frames are
pre-rendered with a 3-level intensity trail (#FF0000, #990000, #440000)
at 14 FPS.
Streaming text was flickering because displayContainer() used raw escape
sequences (\033[%dF) to move the cursor and fmt.Println to repaint on
every token. This bypassed Bubble Tea's synchronized output, cursor
hiding, and atomic flush.
Replace the manual approach with a dedicated tea.Program that runs for
the lifetime of each streaming response. BT's renderer now handles all
in-place updates flicker-free. One-shot messages (user, tool, system,
error) continue to use simple fmt.Println since they are never redrawn.
OpenAI strictly validates JSON Schema and rejects 'required': null (expects
an array). When MCP tools had no required fields, the nil []string serialized
as null. Initialize required as []string{} and sanitize nested schemas
recursively to remove null/invalid required fields from MCP server responses.
Replace catwalk dependency with direct models.dev integration (97 providers,
3039 models vs catwalk's 22/679). Auto-route @ai-sdk/openai-compatible
providers through fantasy's openaicompat using the api URL from models.dev,
eliminating the need for --provider-url. Add --all flag to 'mcphost models'
to show all providers vs just fantasy-compatible ones.
Fix all 74 golangci-lint issues: errcheck (53), staticcheck SA4006 (24),
SA9003 (2), ST1005 (5), ineffassign (3). Restructure styles.go color
handling into a colorScheme struct to eliminate SA4006 false positives
from new(x) syntax.
- Make model validation advisory: unknown models pass through to the
provider API with a stderr warning instead of blocking. Catwalk
metadata is used for cost tracking and suggestions when available.
- Add LookupModel() as the primary registry API (returns nil for
unknown models, no error).
- Add 'mcphost update-models' subcommand to refresh the model database
from a catwalk server (defaults to https://catwalk.charm.sh), a local
file, or reset to the embedded version. Supports ETag caching.
- Add disk cache layer at ~/.local/share/mcphost/providers.json;
registry loads cached data first, falls back to embedded.
- Add vercel provider support via fantasy.
- Add io.Closer plumbing to ProviderResult and Agent.Close() for
providers that hold resources.
- Delete dead ESC listener code and bubbletea/time imports from agent
- Remove internal/tokens/ package (empty stubs and trivial estimator)
- Inline token estimation into usage_tracker as unexported helper
- Remove unused EstimateAndUpdateUsageFromText dead method
- Remove 9 unsupported provider env var entries from registry
Switch the --model / -m flag format from colon-separated (provider:model)
to slash-separated (provider/model), e.g. anthropic/claude-sonnet-4-5-20250929
or ollama/qwen3:8b. The slash separator is cleaner since model names can
contain colons (ollama tags, bedrock ARNs).
Add centralized ParseModelString() in internal/models/providers.go that all
callers now use. The old colon format is still accepted with a deprecation
warning to stderr for backward compatibility.
Update default model to claude-sonnet-4-5-20250929.
Each spinner created a new tea.NewProgram which sent DECRQM queries for
synchronized output mode 2026. When the program exited and restored
cooked terminal mode, the terminal's DECRPM response leaked as visible
^[[?2026;2$y characters. Replace Bubble Tea spinner with a simple
goroutine animation loop writing directly to stderr via lipgloss.
Add support for using Claude models via Google Cloud Vertex AI through
the `google-vertex-anthropic` provider. This enables users who have
Claude access through their Google Cloud account to use mcphost with
Vertex AI authentication.
Changes:
- Add `google-vertex-anthropic` provider case and createVertexAnthropicProvider()
- Support multiple env var names for project/region to match eino-claude:
- Project: ANTHROPIC_VERTEX_PROJECT_ID, GOOGLE_CLOUD_PROJECT, GCLOUD_PROJECT
- Region: CLOUD_ML_REGION (defaults to "global" if not set)
- Upgrade eino from v0.5.11 to v0.7.11 (required by eino-claude v0.1.12)
- Migrate schema API from OpenAPI v3 to JSON Schema (eino v0.7.11 change)
Usage:
# Authenticate with Google Cloud
gcloud auth application-default login
# Set required environment variables
export ANTHROPIC_VERTEX_PROJECT_ID="your-project-id"
export CLOUD_ML_REGION="us-east5" # or use default "global"
# Run mcphost
mcphost --model google-vertex-anthropic:claude-sonnet-4@20250514
Reference: https://docs.anthropic.com/en/docs/claude-code/google-vertex-ai🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* fix: convert JSON Schema draft-07 exclusive bounds to draft-04 format
Chrome DevTools MCP and other MCP servers use JSON Schema draft-07 where
exclusiveMinimum/exclusiveMaximum are numeric values representing the
actual bounds. However, kin-openapi (OpenAPI 3.0) expects these fields
as booleans that modify the minimum/maximum values (draft-04 format).
This fix recursively processes input schemas to convert:
- exclusiveMinimum: N → minimum: N, exclusiveMinimum: true
- exclusiveMaximum: N → maximum: N, exclusiveMaximum: true
Handles nested schemas in properties, items, additionalProperties,
and schema composition keywords (allOf, anyOf, oneOf, not).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: add table-driven tests for JSON Schema draft conversion
Adds comprehensive tests for convertExclusiveBoundsToBoolean():
- Simple exclusiveMinimum/exclusiveMaximum conversion
- Both bounds together
- Already boolean values (draft-04 style, unchanged)
- No exclusive bounds (unchanged)
- Nested properties
- Array items
- allOf composition
- additionalProperties
- Real-world Chrome DevTools MCP schema example
- Invalid JSON handling (returns unchanged)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Adds a new CLI option, `--approve-tool-run` (or via config setting),
that when enabled, prompts the user to approve a tool's execution
before it runs.
This option is disabled by default to maintain existing behavior.
Replace fmt.Printf calls in connection pool health check routines with debug logger calls. Health check messages are now only displayed when the --debug flag is enabled, providing a cleaner terminal output during normal operation while maintaining diagnostic information for troubleshooting.
* feat: add SDK package for programmatic MCPHost usage
- Export InitConfig and LoadConfigWithEnvSubstitution from cmd package
- Create sdk package with MCPHost type for programmatic access
- Add Options struct for configuration overrides
- Implement Prompt and PromptWithCallbacks methods
- Add session management (load, save, clear)
- Create type helpers for Message and ToolCall
- Add comprehensive SDK documentation in README
- Include basic and scripting examples
- Add unit tests for SDK functionality
The SDK reuses all existing internal packages and maintains identical
behavior to the CLI, including config loading, environment variables,
and defaults.
* docs: add SDK section to main README with link to detailed documentation
* fix tests
* update CI