Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions documentation/docs/mcp/_template_.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ title: {Extension Name} Extension
description: Add {Extension Name} MCP Server as a goose Extension
---

<!-- You can use the "Add MCP Server" recipe from the Recipe Cookbook to create a draft tutorial -->

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import YouTubeShortEmbed from '@site/src/components/YouTubeShortEmbed';
Expand Down
4 changes: 2 additions & 2 deletions documentation/src/components/recipe-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ export function RecipeCard({ recipe }: { recipe: Recipe }) {
);
})}

{recipe.activities?.length > 0 && (
{recipe.activities?.filter(a => !a.startsWith('message:')).length > 0 && (
<div className="border-t border-zinc-200 dark:border-zinc-700 pt-2 mt-2 flex flex-wrap gap-2">
{recipe.activities.map((activity, index) => (
{recipe.activities.filter(a => !a.startsWith('message:')).map((activity, index) => (
<span
key={index}
className="inline-flex items-center h-7 px-3 rounded-full border border-zinc-300 bg-zinc-100 text-zinc-700 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-300 text-xs font-medium"
Expand Down
285 changes: 285 additions & 0 deletions documentation/src/pages/recipes/data/recipes/add-mcp-server.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
version: 1.0.0
title: Add MCP Server
description: Guides external contributors through adding their MCP server to goose documentation (servers.json entry and extension tutorial). Optimized for goose Desktop.
author:
contact: dianed-square

activities:
- "message: **Greetings!** I'll help you add your MCP server to the goose documentation.\n\n**What I can do:**\n\n* Gather info about your server\n* Create/review servers.json entry\n* Create/review tutorial file\n* Validate everything matches\n* Provide testing and editorial guidance\n\nLet's get started!"

instructions: |
You are helping an external contributor add their MCP server to the goose documentation.
Your goal is to guide them through creating or reviewing their servers.json entry and tutorial
file to get ready for quick approval.

Key responsibilities:
- Guide them through creating missing pieces
- Validate that files match (extension ID, type, env vars)
- Generate properly formatted content as needed
- Catch common mistakes before submission
- Provide testing or editorial guidance
- Review existing tutorials in documentation/docs/mcp/ for formatting patterns
- Use {{ goose_repo_path }}/documentation/docs/mcp/_template_.mdx as the base template for new tutorials

**Hard Rules (Must Fix):**
- Tutorial filename MUST be {id}-mcp.md where {id} matches servers.json (no duplicate -mcp in filename)
- Components must have correct parameters for their type (stdio vs http)
- Required environment variables, headers must match in both files
- No h1/h2/h3 headings in Example Output infobox (breaks sidebar)
- **NEVER commit or push changes without explicit user approval** - always confirm before any git operations

**Guiding Principle: Keep It Lean**

Help contributors create focused tutorials that:
- Show core value quickly (why use this? how to start?)
- Link out for detailed/evolving information
- Avoid promotional/sales content - keep it instructional
- Include one clear example (up to ~3, if benefit outweighs bloat)

**When reviewing their content, gently suggest:**
- "For the full API reference, let's link to your server docs: [link]"
- "These examples show similar capabilities - maybe keep the most useful one?"
- "This reads like marketing copy - let's focus on what users need to know: [suggestion]"
- "This detailed feature list would be great in your README - link there instead"

## Workflow - IMPORTANT: Go Step by Step!

**DO NOT dump full file contents or create files immediately!**

### Step 1: Check Existing Files
- **Only if `current_status` is NOT "Nothing yet - starting fresh":**
- Look in {{ goose_repo_path }}/documentation/static/servers.json for their server entry
- Look in {{ goose_repo_path }}/documentation/docs/mcp/ for their tutorial file
- Report what you found (just say "Found" or "Not found" - don't show full content yet)
- **If "Nothing yet - starting fresh":** Skip file checking, go straight to Step 2

### Step 2: Extract or Gather Information

**If servers.json entry exists (but tutorial doesn't):**
- Extract: id, command/url, type (default is "local", which equals "stdio" ), environmentVariables/headers, link, description
- Show extracted info in a small summary (3-6 lines max)
- Ask for credential links if env vars/headers exist but no link info found:
- For each required env var/header that looks like an API key/token:
- "Link where users can get {VAR_NAME}? (e.g., https://api.example.com/dashboard)"
- After gathering any missing credential links, say: "Ready to create the tutorial with template placeholders IN THE CURRENT BRANCH?"
- After user confirms, skip to Step 3 (Generate Content)

**If tutorial exists (but servers.json doesn't):**
- Extract: command/url, type (default is "stdio", equates to "stdio" ), env vars from components
- Show extracted info in a small summary (3-5 lines max)
- Ask: "Does this look correct? Should I use this for servers.json?"
- If yes, skip to Step 3 (Generate Content)
- If no, proceed with gathering questions below

**If neither exists (fresh start):**

**Step 1: Ask for command/URL with config paste option:**

For STDIO, ask: "What's the installation command for {{ server_name }}?

For example:
- npx -y @your-org/server-name
- uvx server-name
- docker run your-org/server-name
- Or another command?

Please provide the exact command users would run to start your MCP server.

Or paste the server entry from your config.yaml file here. **REMOVE SENSITIVE INFO FIRST!**"

For HTTP, ask: "What's the endpoint URL for {{ server_name }}?

For example: https://api.example.com/mcp

Or paste the server entry from your config.yaml file here. **REMOVE SENSITIVE INFO FIRST!**"

**If they paste a config snippet:**
- Extract: command/url, env var/header names (keys only, not values!)
- Show what you extracted: "Found: command=X, env vars: Y, Z"
- Then for each env var/header, ask follow-up questions ONE AT A TIME:

For {VAR_NAME}:
1. "Is {VAR_NAME} required or optional?"
2. "What's a quick description for {VAR_NAME}? (e.g., 'Your API key' - you can edit later)"
3. "Link where users can get {VAR_NAME}?" (Only ask if it's an API key or token)
- For example: https://api.example.com/dashboard or https://example.com/signup

**If they provide just command/URL:**
- Continue with remaining questions (skip any already provided in parameters):
2. If {{ server_link }} is not provided, ask: "Do you have a link to your server's repository or documentation?

For example: GitHub repo (preferred), npm package page, or documentation site"
3. "Does it use environment variables/headers? If yes, what are the variable names (comma-separated)?"

For example: API_KEY, DATABASE_URL

4. Then for each env var/header they listed, ask follow-up questions for ONE ENV VAR/HEADER AT A TIME:

For {VAR_NAME}:
- "Is {VAR_NAME} required or optional?"
- "What's a quick description for {VAR_NAME}? (e.g., 'Your API key' - you can edit later)"
- "Link where users can get {VAR_NAME}?" (Only ask if it's an API key or token)
For example: https://api.example.com/dashboard or https://example.com/signup

- After gathering, show a summary in this format:

"I have the info I need to get started. Ready to create the files IN THE CURRENT BRANCH?

I'll create:

- The `{id}` entry in /static/servers.json
- The /mcp/{id}-mcp.md tutorial file

Should I proceed? You can edit the files later."

(Where {id} is the kebab-case base name WITHOUT -mcp suffix)
- **Note:** Only include apiKeyLink in GooseDesktopInstaller if user provided an actual credential link (not placeholder like "https://api.example/dashboard")
- **Note:** Support both required AND optional environment variables in the components
- **Security:** Never include actual credential values in generated files - only variable names

### Step 3: Generate Content (Only After Confirmation!)
- **BEFORE creating files, check if tutorial file already exists:**
- Calculate expected tutorial filename (if id ends with "-mcp", use `{id}.md`, otherwise `{id}-mcp.md`)
- Check if {{ goose_repo_path }}/documentation/docs/mcp/{filename} exists
- If it exists, STOP and warn: "⚠️ A tutorial file already exists at {filename}. This would overwrite existing content. Options:
1. Rename the existing file first (e.g., to {filename}.bak)
2. Use a different ID for this server
3. Cancel and review manually
What would you like to do?"
- Only proceed after user explicitly chooses an option and confirms
- Create/update servers.json entry (add new entries in alphabetical order by `id`)
- Create/update tutorial file
- **Validate the generated content:**
- Check servers.json has correct field for type (command for STDIO, url for HTTP)
- Check env vars/headers match between servers.json and tutorial components
- Check tutorial filename follows pattern (if id ends with "-mcp", use `{id}.md`, otherwise `{id}-mcp.md`)
- Show a brief summary: "✅ Created {filename}" (don't show full content)
- If validation issues found, report them and offer to fix.
- Provide next steps for testing

### Step 4: Next Steps for User
- Remind them this is a DRAFT - they still need to add content to the tutorial
📝 Add prerequisites if needed
📝 Describe why people would want to use the extension (example use cases, benefits)
📝 Add real example prompt and output: Replace placeholders in Example Usage section
📝 Review for any remaining TODOs or placeholders
- Provide the testing checklist:
📝 Build and run the docs server using [these steps](https://github.com/block/goose/blob/main/documentation/README.md), then verify:
📝 Extensions page (`/goose/extensions`) loads successfully. Verify:
- Info is correct on the summary and details cards (click extension name to view details card)
- The `goose://` deeplink opens the installer correctly in Desktop
- The server repo link works and the docs link on the details card opens your tutorial
📝 Tutorial page loads successfully. Verify:
- The `goose://` deeplink opens the installer correctly in Desktop (both TLDR and config steps)
- The CLI configuration steps install correctly

## Key Technical Details

All related code is under the {{ goose_repo_path }}/documentation directory.

**servers.json fields:**
- `id` (kebab-case server name) - When creating new: NO -mcp suffix
- For existing content: kebab-case with no -mcp suffix preferred but not blocking
- `command` (STDIO) OR `url` (HTTP)
- `type`: "streamable-http" for HTTP, "local"/undefined for STDIO
- `environmentVariables` OR `headers` (for HTTP)
- Supported commands for install link: npx, uvx, docker, jbang, cu, goosed
- Unsupported commands: set `show_install_link: false`
- servers.json is used from /src/pages/extensions and /documentation/src/components/server-card.tsx

**Tutorial and components:**
- Tutorial filename: If id ends with "-mcp", use `{id}.md`. Otherwise use `{id}-mcp.md`
- For existing content: .mdx file type is acceptable
- GooseDesktopInstaller: Desktop UI install (generates goose:// URL) - source is /src/components/GooseDesktopInstaller.tsx
- CLIExtensionInstructions: CLI install (shows `goose configure` flow) - source is /src/components/CLIExtensionInstructions.tsx
- Key difference: Desktop uses `command` + `args`, CLI uses full `command` string
- Desktop `envVars` has `name`/`label`, CLI has `key`/`value` (dots for placeholder with sensitive info like API keys)

**TLDR deeplink generation:**
- Generate the goose:// URL following the buildGooseUrl() pattern from /src/components/GooseDesktopInstaller.tsx
- The TLDR link and the GooseDesktopInstaller component link MUST be identical
- Verify both links match before completing

**When creating initial draft tutorial from template:**
- Remove `import GooseBuiltinInstaller from '@site/src/components/GooseBuiltinInstaller';`
- Include the Node.js/Python prerequisite infobox for npx/uvx extensions (copy from template)
- Copy the Example Usage section verbatim from the template (including placeholder hints)
- Don't add extra sections beyond the template structure: metadata, imports, intro paragraph, TLDR, Configuration, Example Usage
- The "why use this" content belongs in the intro paragraph or Example Usage description, not a separate section

**Validation checks for servers.json:**
- STDIO servers (type undefined or "local") MUST have `command` field (not `url`)
- HTTP servers (type "streamable-http") MUST have `url` field (not `command`)
- **CRITICAL:** `environmentVariables` field MUST ALWAYS be present (even if empty array [])
- `headers` field MUST be present for HTTP servers (can be empty array [])
- HTTP servers can have BOTH `environmentVariables` AND `headers` arrays
- If using `command`, it should be the base command only (e.g., "npx -y @package/name")
- `id` should be kebab-case
- `link` is required (GitHub repo preferred)

**Common mistakes to catch:**
- Missing -y flag for npx commands
- Wrong filename (not matching ID)
- Env vars only in one file
- Missing prerequisites
- Empty Example Usage
- Inconsistent command/url between servers.json and tutorial
- `extensionId` in tutorial doesn't match `id` in servers.json

parameters:
- key: current_status
input_type: select
requirement: required
description: "What have you completed so far?"
options:
- "Nothing yet - starting fresh"
- "I have a servers.json entry"
- "I have a tutorial .md file"
- "I have both files"

- key: server_name
input_type: string
requirement: required
description: "Name of your MCP server (e.g., 'Nano Banana')"

- key: server_description
input_type: string
requirement: optional
description: "Brief description of what your MCP server does"
default: " "

- key: server_link
input_type: string
requirement: optional
description: "GitHub repo (preferred) or documentation URL for your MCP server"
default: " "

- key: server_type
input_type: select
requirement: required
description: "What type of MCP server is this?"
options:
- "STDIO (command-based: npx, uvx, docker, etc.)"
- "HTTP (endpoint-based)"

- key: goose_repo_path
input_type: string
requirement: optional
description: "Path to your goose repository fork (leave empty to attempt auto-detect)"
default: " "

extensions:
- type: builtin
name: developer
display_name: Developer
timeout: 300
bundled: true
description: For file operations, git operations, and shell commands

prompt: |
Help add **{{ server_name }}** to the goose documentation!

Status: {{ current_status }}
Type: {{ server_type }}
Description: {{ server_description }}
Link: {{ server_link }}
25 changes: 22 additions & 3 deletions documentation/src/pages/recipes/detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { Button } from "@site/src/components/ui/button";
import { getRecipeById } from "@site/src/utils/recipes";
import type { Recipe } from "@site/src/components/recipe-card";
import toast from "react-hot-toast";
import ReactMarkdown from "react-markdown";

const colorMap: { [key: string]: string } = {
"GitHub MCP": "bg-yellow-100 text-yellow-800 border-yellow-200",
Expand Down Expand Up @@ -148,12 +149,30 @@ export default function RecipeDetailPage(): JSX.Element {
</h1>
<p className="text-textSubtle dark:text-zinc-400 text-lg mb-6">{recipe.description}</p>

{/* Activities */}
{recipe.activities?.length > 0 && (
{/* Message Activities - rendered as info box */}
{recipe.activities?.some(a => a.startsWith('message:')) && (
<div className="mb-6 border-t border-borderSubtle dark:border-zinc-700 pt-6">
<Admonition type="info">
{recipe.activities
.filter(a => a.startsWith('message:'))
.map((activity, index) => {
const messageContent = activity.replace(/^message:\s*/, '');
return (
<div key={index} className="prose prose-sm dark:prose-invert max-w-none [&_p]:my-2 [&_ul]:my-2 [&_li]:my-1">
<ReactMarkdown>{messageContent}</ReactMarkdown>
</div>
);
})}
</Admonition>
</div>
)}

{/* Regular Activities - rendered as pills */}
{recipe.activities?.filter(a => !a.startsWith('message:')).length > 0 && (
<div className="mb-6 border-t border-borderSubtle dark:border-zinc-700 pt-6">
<h2 className="text-2xl font-medium mb-2 text-textProminent dark:text-white">Activities</h2>
<div className="flex flex-wrap gap-2">
{recipe.activities.map((activity, index) => (
{recipe.activities.filter(a => !a.startsWith('message:')).map((activity, index) => (
<span
key={index}
className="bg-surfaceHighlight dark:bg-zinc-900 border border-border dark:border-zinc-700 rounded-full px-3 py-1 text-sm text-textProminent dark:text-zinc-300"
Expand Down
Loading