What Is the MCP Elicitation Process and How Do Goose Extensions Expose Tools?

The MCP elicitation process is a round-trip workflow where a Goose agent pauses tool execution to request missing user data via a JSON schema form, while extensions expose tools through the ExtensionConfig enum which explicitly lists available tool IDs that the agent validates before forwarding to the model.

The block/goose repository implements a Model Context Protocol (MCP) agent framework that handles dynamic user interactions through structured elicitation and modular tool discovery. Understanding how Goose manages incomplete tool parameters through its elicitation flow—and how it registers external capabilities via its extension architecture—reveals the robustness of its MCP implementation. This article examines the source code to explain the technical mechanisms behind both the elicitation process and tool exposure.

Understanding the MCP Elicitation Process

MCP elicitation allows an MCP-enabled Goose agent to request additional information from the user when a tool’s schema cannot be satisfied automatically. According to the block/goose source code, this process involves coordinated interaction between the MCP client, an action manager, and the user interface.

The Five-Step Elicitation Flow

The elicitation process follows a precise sequence implemented across several core files:

  1. Agent detects missing data – When a tool requires parameters that cannot be inferred automatically, the agent initiates an elicitation request.

  2. McpClient::create_elicitation builds the request – Located in crates/goose/src/agents/mcp_client.rs (lines 302-341), this method constructs a JSON schema describing the required data and hands it to the global ActionRequiredManager. The manager generates a unique request ID, wraps the message as an action_required_elicitation type, and transmits it to the client UI or CLI via an unbounded channel.

  3. ActionRequiredManager::request_and_wait blocks for response – In crates/goose/src/action_required_manager.rs (lines 39-78), the manager stores a PendingRequest containing a oneshot channel keyed by the unique ID. It then blocks execution using tokio::time::timeout with a configurable duration, preventing indefinite hangs.

  4. User submits response through UI – In the CLI implementation (crates/goose-cli/src/session/elicitation.rs), the collect_elicitation_input helper renders the prompt and parses the JSON payload. The CLI then calls ActionRequiredManager::submit_response with the request ID and user-provided data, as seen in crates/goose/src/agents/agent.rs (lines 984-1004).

  5. Agent resumes execution – The pending oneshot channel receives the response, create_elicitation returns a successful CreateElicitationResult, and the agent continues the tool call with the completed parameters.

Technical Implementation Details

The elicitation mechanism relies on specific message types and timeout handling:

  • Message Type: The request is sent as a MessageContent::action_required_elicitation, which the UI renders as a dynamic form based on the supplied JSON schema.
  • Timeout Protection: The system enforces time limits via tokio::time::timeout, ensuring the session does not hang if the user fails to respond.
  • Model Agnostic: Any provider enabling the elicitation capability can participate in this workflow, making it extensible across different LLM backends.

Triggering an Elicitation from Tool Code

The following Rust example demonstrates how an MCP-enabled tool initiates the elicitation flow:

// Inside an MCP‑enabled tool implementation
let request = CreateElicitationRequestParams::FormElicitationParams {
    message: "Please upload a CSV file".into(),
    requested_schema: json_schema, // serde_json::Value describing the form
    ..Default::default()
};

let result = mcp_client.create_elicitation(request, ctx).await?;
match result.action {
    ElicitationAction::Accept => {
        let user_data = result.content.unwrap(); // JSON supplied by the user
        // Continue tool logic with `user_data`
    }
    _ => return Err(anyhow!("User declined elicitation")),
}

This implementation in mcp_client.rs handles the complex coordination of building schemas and managing the blocking wait for user input.

How Goose Extensions Expose Tools

Goose extensions define tool providers through a structured configuration system that explicitly advertises available capabilities to the agent. This ensures the model only invokes tools that are properly registered and validated.

The ExtensionConfig Enum

Extensions are defined by the ExtensionConfig enum in crates/goose/src/agents/extension.rs (lines 150-204). This enum supports multiple transport mechanisms, each specifying how tools are advertised:

  • Stdio – Executes a local command (cmd + args) in a subprocess. The available_tools field lists the tool names the command implements.
  • Builtin – Runs built-in MCP server extensions (e.g., memory, computer-controller) shipped with Goose. The available_tools vector enumerates the tool IDs provided by the service.
  • Platform – Executes inside the agent process with direct Rust API access. available_tools lists the IDs of Rust functions exposed to the model.
  • StreamableHttp – Calls remote HTTP endpoints following the MCP Streamable HTTP specification. available_tools enumerates the tool names the remote service offers.
  • Sse – Legacy Server-Sent-Events transport maintained for configuration compatibility (deprecated, no tool list).

Common fields across all variants include:

  • name – Session identifier
  • description – User-facing UI text
  • available_toolsExplicit list of tool IDs validated before forwarding
  • bundled – Boolean indicating if the extension runs inside the bundled MCP server
  • timeout – Optional per-extension execution limit

Tool Registration Flow

When a session initializes, ExtensionManager (in crates/goose/src/agents/extension_manager.rs) reads the ExtensionConfig objects from the recipe, validates environment variables, starts required subprocesses for Stdio variants, and registers the declared tools. The manager injects these tool descriptors into the MCP client’s Initialize request, ensuring the model receives an accurate capability catalog.

Defining Extensions in Configuration

The following TOML configuration demonstrates enabling a built-in memory extension:


# In a Goose recipe (YAML) – enable a builtin memory extension

extensions:
  - type: builtin
    name: memory
    description: "In‑session vector memory"
    available_tools: ["memory.search", "memory.add"]
    bundled: true

The Builtin variant in ExtensionConfig (lines 188-203) stores these available_tools, which the extension manager registers as callable MCP tools during session startup.

Programmatic Extension Definition

For external tools requiring subprocess execution, use the Stdio variant:

ExtensionConfig::Stdio {
    name: "pdf_extractor".into(),
    description: "Extract text from PDF".into(),
    cmd: "python".into(),
    args: vec!["extract.py".into()],
    available_tools: vec!["pdf.extract".into()],
    envs: Envs::default(),
    timeout: Some(30),
    ..Default::default()
}

This configuration in crates/goose/src/agents/extension.rs (lines 167-187) directs Goose to spawn python extract.py, while explicitly advertising only the "pdf.extract" tool to the model.

Summary

  • MCP elicitation is a structured round-trip where McpClient::create_elicitation pauses execution to request user data via ActionRequiredManager, which blocks on a oneshot channel until submit_response receives the JSON payload.
  • Timeout protection prevents indefinite hangs through tokio::time::timeout enforced in ActionRequiredManager::request_and_wait.
  • Extensions expose tools through the ExtensionConfig enum's available_tools field, which explicitly lists valid tool IDs that the agent validates before forwarding to any model.
  • Registration flow involves ExtensionManager reading configurations, starting subprocesses if needed, and injecting tool descriptors into the MCP client's initialization sequence.

Frequently Asked Questions

What triggers an MCP elicitation request in Goose?

An elicitation request triggers when an MCP-enabled tool detects that required parameters cannot be satisfied automatically from the context or conversation history. The agent then calls McpClient::create_elicitation in crates/goose/src/agents/mcp_client.rs to build a JSON schema form for the user to complete before execution resumes.

How does the ActionRequiredManager prevent indefinite blocking?

The manager enforces timeouts using tokio::time::timeout within the request_and_wait method (lines 39-78 of crates/goose/src/action_required_manager.rs). If the user fails to respond within the configured duration, the pending request expires and the elicitation returns an error, allowing the agent to fail gracefully rather than hanging indefinitely.

What distinguishes Builtin from Stdio extensions?

Builtin extensions run as internal MCP servers bundled within the Goose binary, accessing Rust APIs directly and listing their tools in available_tools. Stdio extensions spawn external subprocesses (like Python scripts) and communicate via standard input/output, requiring Goose to manage the process lifecycle while still validating calls against the explicitly declared available_tools list.

How does Goose validate tool calls against extensions?

Before forwarding any tool invocation to an extension, Goose validates the requested tool ID against the available_tools vector defined in the extension's ExtensionConfig. This verification occurs in the agent layer, ensuring the model can only invoke tools explicitly advertised during the session initialization managed by ExtensionManager.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →