Difference Between stdio and streamable-http Extensions in Goose: MCP Transport Guide

stdio extensions spawn local child processes and communicate over standard input/output pipes, while streamable_http extensions connect to remote MCP servers via HTTP using the Streamable HTTP protocol.

The Goose agent framework (block/goose) supports two distinct transport mechanisms for Model Context Protocol (MCP) extensions. Understanding the architectural differences between stdio and streamable-http extensions helps developers choose the right integration strategy for local tools versus remote services.

Core Architectural Differences

Aspect stdio streamable_http
Transport Layer Uses rmcp::transport::stdio—a bidirectional pipe that feeds the MCP protocol through the child’s stdin and stdout Uses rmcp::transport::streamable_http_client—an HTTP client (reqwest) that follows the Streamable HTTP MCP specification
Process Management Goose spawns a local binary or runs the command inside a Docker container; the binary implements the MCP server side No local process is started; Goose acts as an HTTP client talking to an already-running MCP server reachable at a URI
Configuration Fields cmd, args, envs, env_keys, timeout uri, headers, envs, env_keys, timeout
CLI Interface --with-extension "<env>=... command args" --with-streamable-http-extension "<url> [timeout=...]"
Typical Use Case Quick, self-contained tools (e.g., uvx mcp_gdrive@latest) or extensions needing direct OS access Cloud-hosted MCP services or internal HTTP-based tools (e.g., Asana integration)

Both variants are defined in the ExtensionConfig enum located in crates/goose/src/agents/extension.rs at lines 167–236, and both are handled by the same extension manager logic in extension_manager.rs.

Configuration Structure in extension.rs

The ExtensionConfig enum distinguishes the two transport types through distinct struct variants. In crates/goose/src/agents/extension.rs, the definitions specify which fields are required for each transport:

pub enum ExtensionConfig {
    #[serde(rename = "stdio")]
    Stdio {
        name: String,
        description: String,
        cmd: String,
        args: Vec<String>,
        envs: Envs,
        env_keys: Vec<String>,
        timeout: Option<u64>,
        bundled: Option<bool>,
        available_tools: Vec<String>,
    },

    #[serde(rename = "streamable_http")]
    StreamableHttp {
        name: String,
        description: String,
        uri: String,
        envs: Envs,
        env_keys: Vec<String>,
        headers: HashMap<String, String>,
        timeout: Option<u64>,
        bundled: Option<bool>,
        available_tools: Vec<String>,
    },
}

The Stdio variant requires a cmd field specifying the executable path, while StreamableHttp requires a uri field pointing to the HTTP endpoint. Validation logic in crates/goose/src/agents/validate_extensions.rs enforces these requirements, ensuring stdio extensions have a valid command and streamable_http extensions have a valid URI.

Transport Implementation Details

Stdio Extension Lifecycle

When Goose initializes a stdio extension, it spawns a child process and establishes bidirectional pipes. In crates/goose/src/agents/extension_manager.rs at lines 700–730, the ExtensionConfig::Stdio match arm creates the transport:

ExtensionConfig::Stdio {
    name,
    cmd,
    args,
    envs,
    env_keys,
    timeout,
    ..
} => {
    // Spawn child with environment variables and arguments
    let child = Command::new(&cmd)
        .args(&args)
        .envs(envs.get_env())
        .stdin(Stdio::null())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()?;
    
    let transport = stdio::StdIOTransport::new(child);
    // Connect the MCP client using the stdio transport...
}

This approach runs the MCP server as a separate process, with Docker isolation available via the --container flag for enhanced security.

Streamable-HTTP Client Setup

For HTTP-based extensions, Goose builds a reqwest client and configures the StreamableHttpClientTransport. The implementation in extension_manager.rs at lines 306–380 handles the ExtensionConfig::StreamableHttp variant:

ExtensionConfig::StreamableHttp {
    name,
    uri,
    headers,
    timeout,
    ..
} => {
    let client = reqwest::Client::builder()
        .default_headers(build_headers(headers))
        .build()?;
    
    let transport = StreamableHttpClientTransport::with_client(
        client,
        StreamableHttpClientTransportConfig { uri: uri.into(), ..Default::default() },
    );
    // Connect the MCP client with optional OAuth fallback...
}

If the remote server returns an authentication challenge, Goose initiates an OAuth flow defined in extension_manager.rs at lines 558–590.

CLI Usage and Practical Examples

Running Local Tools with Stdio

Use the --with-extension flag to launch local executables. The CLI parser in crates/goose-cli/src/session/mod.rs (lines 300–320) handles environment variable assignments and command parsing:

goose run \
  --with-extension "MY_API_KEY=abc123 uvx mcp_gdrive@latest" \
  --recipe my_recipe.yaml

This string parses into an ExtensionConfig::Stdio with:

  • cmd: "uvx"
  • args: ["mcp_gdrive@latest"]
  • envs: {MY_API_KEY: "abc123"}

Connecting to Remote HTTP Endpoints

Use the --with-streamable-http-extension flag for remote services. The parser in crates/goose-cli/src/cli.rs (lines 17–40) extracts the URL and optional timeout:

goose run \
  --with-streamable-http-extension "https://mcp.asana.com/mcp timeout=120" \
  --recipe my_recipe.yaml

This creates an ExtensionConfig::StreamableHttp with the specified URI and a 120-second timeout.

Programmatic Extension Configuration

Factory methods in crates/goose/src/agents/extension.rs (lines 295–322) allow programmatic construction:

use goose::agents::ExtensionConfig;

// Stdio extension for local Google Drive integration
let stdio_ext = ExtensionConfig::stdio(
    "gdrive",
    "uvx",
    "Google Drive MCP",
    300_u64,
).with_args(vec!["mcp_gdrive@latest"]);

// Streamable HTTP extension for Asana
let http_ext = ExtensionConfig::streamable_http(
    "asana",
    "https://mcp.asana.com/mcp",
    "Asana MCP",
    300_u64,
);

Key Implementation Files

Understanding the full lifecycle requires examining these specific source files:

Summary

  • stdio extensions launch local executables and communicate over standard input/output pipes, making them ideal for self-contained tools requiring OS access.
  • streamable-http extensions connect to remote HTTP endpoints using the Streamable HTTP protocol, suitable for cloud-hosted MCP services without spawning local processes.
  • Both types are defined in extension.rs, managed by extension_manager.rs, and interchangeable in Goose recipes.
  • stdio requires a cmd field and spawns a child process with optional Docker isolation.
  • streamable-http requires a uri field and uses a reqwest client with optional OAuth authentication.

Frequently Asked Questions

When should I choose stdio over streamable-http extensions?

Choose stdio when you need to run local tools that require filesystem access, environment variables, or specific system binaries—such as uvx packages or Docker containers. Choose streamable-http when integrating with cloud-hosted services, serverless functions, or internal HTTP APIs that already expose an MCP endpoint, eliminating the need to manage local process lifecycles.

Can streamable-http extensions run in Docker containers?

No. streamable-http extensions do not spawn local processes, so Docker isolation does not apply. They function purely as HTTP clients connecting to external servers. If you need containerized execution, use a stdio extension with the --container flag, which runs the command inside a Docker container as implemented in extension_manager.rs.

How does authentication work for HTTP-based extensions?

Goose handles authentication for streamable-http extensions through an OAuth fallback mechanism. If the remote MCP server responds with an authentication challenge, the code in extension_manager.rs (lines 558–590) initiates an OAuth flow. You can also pass static headers (such as API keys) via the headers field in the configuration.

Is it possible to use both extension types simultaneously?

Yes. Goose supports mixing stdio and streamable-http extensions within the same session. You can pass multiple --with-extension and --with-streamable-http-extension flags to the CLI, or construct both variants programmatically and add them to the same agent configuration. The extension manager initializes each transport independently while exposing all tools to the Goose agent uniformly.

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 →