diff --git a/documentation/docs/docker/Dockerfile b/documentation/docs/docker/Dockerfile
index c76e4c75d6..ac72633df6 100644
--- a/documentation/docs/docker/Dockerfile
+++ b/documentation/docs/docker/Dockerfile
@@ -74,6 +74,8 @@ RUN apt-get update && apt-get install -y \
# Python
python3 \
python3-pip \
+ # Java
+ openjdk-17-jdk \
# Additional tools
tree \
tmux \
@@ -82,6 +84,9 @@ RUN apt-get update && apt-get install -y \
# Install uv using curl
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
+# Install JBang using curl
+RUN curl -Ls https://sh.jbang.dev | bash -s - app setup
+
# Copy the built binaries
COPY --from=builder /usr/src/goose/target/release/goose /usr/local/bin/
@@ -109,3 +114,6 @@ RUN git config --global init.defaultBranch main && \
# Add some helpful aliases
RUN echo 'alias ll="ls -la"' >> ~/.bashrc && \
echo 'alias fd=fdfind' >> ~/.bashrc
+
+# Add JBang to PATH
+RUN echo 'export PATH="$HOME/.jbang/bin:$PATH"' >> ~/.bashrc
\ No newline at end of file
diff --git a/documentation/docs/getting-started/using-extensions.md b/documentation/docs/getting-started/using-extensions.md
index 808d633f11..32bd77638f 100644
--- a/documentation/docs/getting-started/using-extensions.md
+++ b/documentation/docs/getting-started/using-extensions.md
@@ -131,6 +131,8 @@ See available servers in the **[MCP Server Directory](https://www.pulsemcp.com/s
#### Example of adding the [Knowledge Graph Memory MCP Server](https://github.com/modelcontextprotocol/servers/tree/main/src/memory):
+
+
```
┌ goose-configure
│
@@ -155,6 +157,74 @@ See available servers in the **[MCP Server Directory](https://www.pulsemcp.com/s
└ Added Knowledge Graph Memory extension
```
+
+
+
+ ```
+ ┌ goose-configure
+ │
+ ◇ What would you like to configure?
+ │ Add Extension
+ │
+ ◇ What type of extension would you like to add?
+ │ Command-line Extension
+ │
+ ◇ What would you like to call this extension?
+ │ Wikipedia Reader
+ │
+ ◇ What command should be run?
+ │ uvx mcp-wiki
+ │
+ ◇ Please set the timeout for this tool (in secs):
+ │ 300
+ │
+ ◆ Would you like to add environment variables?
+ │ No
+ │
+ └ Added Wikipedia Reader extension
+ ```
+
+
+
+
+Note: Java and Kotlin extensions are only support on Linux and macOS
+
+ ```
+ ┌ goose-configure
+ │
+ ◇ What would you like to configure?
+ │ Add Extension
+ │
+ ◇ What type of extension would you like to add?
+ │ Command-line Extension
+ │
+ ◇ What would you like to call this extension?
+ │ Spring Data Explorer
+ │
+ ◇ What command should be run?
+ │ jbang -Dspring.profiles.active=dev org.example:spring-data-mcp:1.0.0
+ │
+ ◇ Please set the timeout for this tool (in secs):
+ │ 300
+ │
+ ◆ Would you like to add environment variables?
+ │ Yes
+ │
+ ◇ Environment variable name:
+ │ SPRING_DATASOURCE_URL
+ │
+ ◇ Environment variable value:
+ │ jdbc:postgresql://localhost:5432/mydb
+ │
+ ◇ Add another environment variable?
+ │ No
+ │
+ └ Added Spring Data Explorer extension
+ ```
+
+
+
+
diff --git a/documentation/docs/tutorials/_template_.md b/documentation/docs/tutorials/_template_.md
index 7364f52645..8a5f8021fa 100644
--- a/documentation/docs/tutorials/_template_.md
+++ b/documentation/docs/tutorials/_template_.md
@@ -36,6 +36,10 @@ Note that you'll need [Node.js](https://nodejs.org/) installed on your system to
Note that you'll need [uv](https://docs.astral.sh/uv/#installation) installed on your system to run this command, as it uses `uvx`.
:::
+:::info
+Note that you'll need [JBang](https://www.jbang.dev/download) installed on your system to run this command, as it uses `jbang`.
+:::
+
1. Run the `configure` command:
diff --git a/documentation/src/utils/install-links.ts b/documentation/src/utils/install-links.ts
index 156fee79f6..98a47a2e8a 100644
--- a/documentation/src/utils/install-links.ts
+++ b/documentation/src/utils/install-links.ts
@@ -11,7 +11,7 @@ export function getGooseInstallLink(server: MCPServer): string {
return `goose://extension?${queryParams}`;
}
const parts = server.command.split(" ");
- const baseCmd = parts[0]; // npx or uvx
+ const baseCmd = parts[0]; // jbang, npx or uvx
const args = parts.slice(1); // remaining arguments
const queryParams = [
`cmd=${encodeURIComponent(baseCmd)}`,
diff --git a/ui/desktop/src/agent/utils.tsx b/ui/desktop/src/agent/utils.tsx
index a834126fb7..17f36a4f41 100644
--- a/ui/desktop/src/agent/utils.tsx
+++ b/ui/desktop/src/agent/utils.tsx
@@ -19,6 +19,7 @@ export async function initializeAgent(model: string, provider: string) {
export async function replaceWithShims(cmd: string) {
const binaryPathMap: Record = {
goosed: await window.electron.getBinaryPath('goosed'),
+ jbang: await window.electron.getBinaryPath('jbang'),
npx: await window.electron.getBinaryPath('npx'),
uvx: await window.electron.getBinaryPath('uvx'),
};
diff --git a/ui/desktop/src/bin/jbang b/ui/desktop/src/bin/jbang
new file mode 100755
index 0000000000..8748b44db8
--- /dev/null
+++ b/ui/desktop/src/bin/jbang
@@ -0,0 +1,89 @@
+#!/bin/bash
+
+# Enable strict mode to exit on errors and unset variables
+set -euo pipefail
+
+# Set log file
+LOG_FILE="/tmp/mcp.log"
+
+# Clear the log file at the start
+> "$LOG_FILE"
+
+# Function for logging
+log() {
+ local MESSAGE="$1"
+ echo "$(date +'%Y-%m-%d %H:%M:%S') - $MESSAGE" | tee -a "$LOG_FILE"
+}
+
+# Trap errors and log them before exiting
+trap 'log "An error occurred. Exiting with status $?."' ERR
+
+log "Starting jbang setup script."
+
+# Ensure ~/.config/goose/mcp-hermit/bin exists
+log "Creating directory ~/.config/goose/mcp-hermit/bin if it does not exist."
+mkdir -p ~/.config/goose/mcp-hermit/bin
+
+# Change to the ~/.config/goose/mcp-hermit directory
+log "Changing to directory ~/.config/goose/mcp-hermit."
+cd ~/.config/goose/mcp-hermit
+
+# Check if hermit binary exists and download if not
+if [ ! -f ~/.config/goose/mcp-hermit/bin/hermit ]; then
+ log "Hermit binary not found. Downloading hermit binary."
+ curl -fsSL "https://github.com/cashapp/hermit/releases/download/stable/hermit-$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/').gz" \
+ | gzip -dc > ~/.config/goose/mcp-hermit/bin/hermit && chmod +x ~/.config/goose/mcp-hermit/bin/hermit
+ log "Hermit binary downloaded and made executable."
+else
+ log "Hermit binary already exists. Skipping download."
+fi
+
+log "setting hermit cache to be local for MCP servers"
+mkdir -p ~/.config/goose/mcp-hermit/cache
+export HERMIT_STATE_DIR=~/.config/goose/mcp-hermit/cache
+
+# Update PATH
+export PATH=~/.config/goose/mcp-hermit/bin:$PATH
+log "Updated PATH to include ~/.config/goose/mcp-hermit/bin."
+
+# Initialize hermit
+log "Initializing hermit."
+hermit init >> "$LOG_FILE"
+
+# Install OpenJDK using hermit
+log "Installing OpenJDK with hermit."
+hermit install openjdk@17 >> "$LOG_FILE"
+
+# Download and install jbang if not present
+if [ ! -f ~/.config/goose/mcp-hermit/bin/jbang ]; then
+ log "Downloading and installing jbang."
+ curl -Ls https://sh.jbang.dev | bash -s - app setup
+ cp ~/.jbang/bin/jbang ~/.config/goose/mcp-hermit/bin/
+fi
+
+# Verify installations
+log "Verifying installation locations:"
+log "hermit: $(which hermit)"
+log "java: $(which java)"
+log "jbang: $(which jbang)"
+
+# Check for custom registry settings
+log "Checking for GOOSE_JBANG_REGISTRY environment variable for custom jbang registry setup..."
+if [ -n "${GOOSE_JBANG_REGISTRY:-}" ] && curl -s --head --fail "$GOOSE_JBANG_REGISTRY" > /dev/null; then
+ log "Checking custom goose registry availability: $GOOSE_JBANG_REGISTRY"
+ log "$GOOSE_JBANG_REGISTRY is accessible. Setting it as JBANG_REPO."
+ export JBANG_REPO="$GOOSE_JBANG_REGISTRY"
+else
+ log "GOOSE_JBANG_REGISTRY is not set or not accessible. Using default jbang repository."
+fi
+
+# Trust all jbang scripts that a user might install. Without this, Jbang will attempt to
+# prompt the user to trust each script. However, Goose does not surfact this modal and without
+# user input, the addExtension method will timeout and fail.
+jbang --quiet trust add *
+
+# Final step: Execute jbang with passed arguments, always including --fresh and --quiet
+log "Executing 'jbang' command with arguments: $*"
+jbang --fresh --quiet "$@" || log "Failed to execute 'jbang' with arguments: $*"
+
+log "jbang setup script completed successfully."
\ No newline at end of file
diff --git a/ui/desktop/src/components/settings_v2/extensions/index.ts b/ui/desktop/src/components/settings_v2/extensions/index.ts
index 508afaac0d..ff33ffa6e2 100644
--- a/ui/desktop/src/components/settings_v2/extensions/index.ts
+++ b/ui/desktop/src/components/settings_v2/extensions/index.ts
@@ -245,7 +245,7 @@ export async function addExtensionFromDeepLink(
}
// Validate that the command is one of the allowed commands
- const allowedCommands = ['npx', 'uvx', 'goosed'];
+ const allowedCommands = ['jbang', 'npx', 'uvx', 'goosed'];
if (!allowedCommands.includes(cmd)) {
handleError(
`Failed to install extension: Invalid command: ${cmd}. Only ${allowedCommands.join(', ')} are allowed.`,
diff --git a/ui/desktop/src/extensions.tsx b/ui/desktop/src/extensions.tsx
index 62680423e3..0c607ca4d7 100644
--- a/ui/desktop/src/extensions.tsx
+++ b/ui/desktop/src/extensions.tsx
@@ -239,6 +239,7 @@ export async function loadAndAddStoredExtensions() {
export async function replaceWithShims(cmd: string) {
const binaryPathMap: Record = {
goosed: await window.electron.getBinaryPath('goosed'),
+ jbang: await window.electron.getBinaryPath('jbang'),
npx: await window.electron.getBinaryPath('npx'),
uvx: await window.electron.getBinaryPath('uvx'),
};
@@ -299,7 +300,7 @@ export async function addExtensionFromDeepLink(
}
// Validate that the command is one of the allowed commands
- const allowedCommands = ['npx', 'uvx', 'goosed'];
+ const allowedCommands = ['jbang', 'npx', 'uvx', 'goosed'];
if (!allowedCommands.includes(cmd)) {
handleError(
`Failed to install extension: Invalid command: ${cmd}. Only ${allowedCommands.join(', ')} are allowed.`,