diff --git a/documentation/docs/assets/guides/custom-extension-mcp-inspector.png b/documentation/docs/assets/guides/custom-extension-mcp-inspector.png
index e8887269c6f3..07613f1c98eb 100644
Binary files a/documentation/docs/assets/guides/custom-extension-mcp-inspector.png and b/documentation/docs/assets/guides/custom-extension-mcp-inspector.png differ
diff --git a/documentation/docs/tutorials/custom-extensions.md b/documentation/docs/tutorials/custom-extensions.md
index a117a6840b31..18728179bc05 100644
--- a/documentation/docs/tutorials/custom-extensions.md
+++ b/documentation/docs/tutorials/custom-extensions.md
@@ -4,6 +4,8 @@ description: Create your own custom MCP Server to use as a goose extension
---
import { PanelLeft } from 'lucide-react';
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
# Building Custom Extensions with goose
@@ -12,14 +14,21 @@ goose allows you to extend its functionality by creating your own custom extensi
In this guide, we build an MCP server using the [Python SDK for MCP][mcp-python]. We’ll demonstrate how to create an MCP server that reads Wikipedia articles and converts them to Markdown, integrate it as an extension in goose. You can follow a similar process to develop your own custom extensions for goose.
-You can checkout other examples in this [MCP servers repository][mcp-servers]. MCP SDKs are also available in [Typescript][mcp-typescript] and [Kotlin][mcp-kotlin].
+You can check out other example servers in the [MCP servers repository][mcp-servers]. MCP SDKs are also available for other common languages, such as [Typescript][mcp-typescript] and [Kotlin][mcp-kotlin].
:::info
+goose supports Tools, Resources, and Prompts from the [Model Context Protocol](https://modelcontextprotocol.io/). See [`mcp_client.rs`](https://github.com/block/goose/blob/main/crates/goose/src/agents/mcp_client.rs) for the supported protocol version and client capabilities.
+:::
-goose currently supports Tools and Resources for [MCP Server features](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/).
-We will be adding support for MCP Prompts soon.
+---
-:::
+## Prerequisites
+
+Before you begin, ensure you have the following installed on your system:
+
+- **Python 3.13 or higher** - Required for the MCP server
+- **[uv](https://docs.astral.sh/uv/)** - Python package manager used in this tutorial
+- **Node.js and npm** - Only required if you want to use the MCP Inspector development tool in [Step 4](#step-4-test-your-mcp-server).
---
@@ -44,13 +53,12 @@ Your project directory structure should look like this:
.
├── README.md
├── pyproject.toml
-├── src
-│ └── mcp_wiki
-│ ├── __init__.py # Primary CLI entry point
-│ ├── __main__.py # To enable running as a Python module
-│ ├── py.typed # Indicates the package supports type hints
-│ └── server.py # Your MCP server code (tool, resources, prompts)
-└── uv.lock
+└── src
+ └── mcp_wiki
+ ├── __init__.py # Primary CLI entry point
+ ├── __main__.py # To enable running as a Python module
+ ├── py.typed # Indicates the package supports type hints
+ └── server.py # Your MCP server code (tool, resources, prompts)
```
---
@@ -72,6 +80,7 @@ import requests
from requests.exceptions import RequestException
from bs4 import BeautifulSoup
from html2text import html2text
+from urllib.parse import urlparse
from mcp.server.fastmcp import FastMCP
from mcp.shared.exceptions import McpError
@@ -93,12 +102,24 @@ def read_wikipedia_article(url: str) -> str:
if not url.startswith("http"):
raise ValueError("URL must start with http or https.")
- response = requests.get(url, timeout=10)
+ # SSRF protection: only allow Wikipedia domains
+ parsed = urlparse(url)
+ hostname = parsed.netloc.lower()
+
+ # Allow wikipedia.org or *.wikipedia.org subdomains only
+ if hostname != 'wikipedia.org' and not hostname.endswith('.wikipedia.org'):
+ raise ValueError(f"Only Wikipedia URLs are allowed. Got: {parsed.netloc}")
+
+ # Add User-Agent header to avoid 403 from Wikipedia
+ headers = {
+ 'User-Agent': 'MCP-Wiki/1.0 (Educational purposes; Python requests)'
+ }
+ response = requests.get(url, headers=headers, timeout=10)
if response.status_code != 200:
raise McpError(
ErrorData(
- INTERNAL_ERROR,
- f"Failed to retrieve the article. HTTP status code: {response.status_code}"
+ code=INTERNAL_ERROR,
+ message=f"Failed to retrieve the article. HTTP status code: {response.status_code}"
)
)
@@ -107,8 +128,8 @@ def read_wikipedia_article(url: str) -> str:
if not content_div:
raise McpError(
ErrorData(
- INVALID_PARAMS,
- "Could not find the main content on the provided Wikipedia URL."
+ code=INVALID_PARAMS,
+ message="Could not find the main content on the provided Wikipedia URL."
)
)
@@ -117,11 +138,11 @@ def read_wikipedia_article(url: str) -> str:
return markdown_text
except ValueError as e:
- raise McpError(ErrorData(INVALID_PARAMS, str(e))) from e
+ raise McpError(ErrorData(code=INVALID_PARAMS, message=str(e))) from e
except RequestException as e:
- raise McpError(ErrorData(INTERNAL_ERROR, f"Request error: {str(e)}")) from e
+ raise McpError(ErrorData(code=INTERNAL_ERROR, message=f"Request error: {str(e)}")) from e
except Exception as e:
- raise McpError(ErrorData(INTERNAL_ERROR, f"Unexpected error: {str(e)}")) from e
+ raise McpError(ErrorData(code=INTERNAL_ERROR, message=f"Unexpected error: {str(e)}")) from e
```
### `__init__.py`
@@ -164,9 +185,9 @@ description = "MCP Server for Wikipedia"
readme = "README.md"
requires-python = ">=3.13"
dependencies = [
- "beautifulsoup4>=4.12.3",
- "html2text>=2024.2.26",
- "mcp[cli]>=1.2.0",
+ "beautifulsoup4>=4.14.0",
+ "html2text>=2025.4.15",
+ "mcp[cli]>=1.25.0",
"requests>=2.32.3",
]
@@ -182,7 +203,13 @@ build-backend = "hatchling.build"
## Step 4: Test Your MCP Server
-### Using MCP Inspector
+Verify that your MCP server is running in the MCP Inspector (a browser-based development tool) or the server CLI.
+
+
+
+:::info
+MCP Inspector requires Node.js and npm installed on your computer.
+:::
1. Setup the project environment:
@@ -196,34 +223,42 @@ build-backend = "hatchling.build"
source .venv/bin/activate
```
-3. Run your server in development mode:
+3. Run your server in development mode:
```bash
mcp dev src/mcp_wiki/server.py
```
+
+ MCP Inspector should open automatically in your browser. On first run, you'll be prompted to install `@modelcontextprotocol/inspector`.
-4. Go to `http://localhost:5173` in your browser to open the MCP Inspector UI.
+4. Test the tool:
+ 1. Click `Connect` to initialize your MCP server
+ 2. On the `Tools` tab, click `List Tools` and click the `read_wikipedia_article` tool
+ 3. Enter `https://en.wikipedia.org/wiki/Bangladesh` for the URL and click `Run Tool`
-5. In the UI, you can click "Connect" to initialize your MCP server. Then click on "Tools" tab > "List Tools" and you should see the `read_wikipedia_article` tool.
- Then you can try to call the `read_wikipedia_article` tool with URL set to "https://en.wikipedia.org/wiki/Bangladesh" and click "Run Tool".
+[](../assets/guides/custom-extension-mcp-inspector.png)
-
+
+
+1. Setup the project environment:
-### Testing the CLI
+ ```bash
+ uv sync
+ ```
-1. Install your project locally:
+2. Activate your virtual environment:
```bash
- uv pip install .
+ source .venv/bin/activate
```
-
-2. Check the executable in your virtual environment:
+
+3. Install your project locally:
```bash
- ls .venv/bin/ # Verify your CLI is available
+ uv pip install .
```
-3. Test the CLI:
+4. Verify the CLI:
```bash
mcp-wiki --help
@@ -240,6 +275,8 @@ build-backend = "hatchling.build"
options:
-h, --help show this help message and exit
```
+
+
---
@@ -247,11 +284,17 @@ build-backend = "hatchling.build"
To add your MCP server as an extension in goose:
-1. Click the button in the top-left to open the sidebar
-2. Click `Extensions` in the sidebar
-3. Set the `Type` to `STDIO`
-4. Provide a name and description for your extension
-5. In the `Command` field, provide the absolute path to your executable:
+1. Build the extension binary:
+
+ ```bash
+ uv pip install .
+ ```
+
+2. Open goose Desktop and click the button in the top-left to open the sidebar
+3. Click `Extensions` in the sidebar
+4. Set the `Type` to `STDIO`
+5. Provide a name and description for your extension
+6. In the `Command` field, provide the absolute path to your executable:
```plaintext
uv run /full/path/to/mcp-wiki/.venv/bin/mcp-wiki
```
@@ -261,7 +304,11 @@ To add your MCP server as an extension in goose:
uv run /Users/smohammed/Development/mcp/mcp-wiki/.venv/bin/mcp-wiki
```
-For the purposes on this guide, we'll run the local version. Alternatively, you can publish your package to PyPI. Once published, the server can be run directly using `uvx`. For example:
+:::tip Rebuild binary after changes
+To see any changes you make to your MCP server code after integrating with goose, re-run `uv pip install .` and then restart goose Desktop.
+:::
+
+For the purposes of this guide, we'll run the local version. Alternatively, you can publish your package to PyPI. Once published, the server can be run directly using `uvx`. For example:
```
uvx mcp-wiki