Skip to content

feat(discovery): AI Catalog / Server Card auto-discovery (SEP-2127) — CLI + Desktop#1

Draft
tadasant wants to merge 2 commits into
mainfrom
feature/ai-catalog-server-card-discovery
Draft

feat(discovery): AI Catalog / Server Card auto-discovery (SEP-2127) — CLI + Desktop#1
tadasant wants to merge 2 commits into
mainfrom
feature/ai-catalog-server-card-discovery

Conversation

@tadasant

@tadasant tadasant commented Jun 27, 2026

Copy link
Copy Markdown
Owner

AI Catalog / Server Card auto-discovery (SEP-2127) — CLI + Desktop

Experimental, opt-in feature: a background "sidecar" watches the domains a Goose session touches, probes them for an AI Catalog (GET https://<domain>/.well-known/ai-catalog.json) and the Server Cards / Skills bundles it references, and surfaces a non-intrusive, one-click install nudge — without ever blocking the agent loop. Installing a discovered MCP server (running its auth flow if the card requires one) makes it active in the same session, immediately callable.

Implements the SEP-2127 "Server Cards" / AI Catalog discovery concept on both surfaces: the Goose CLI and the Goose Desktop app.

Demo branch on a fork; opening as a draft for discussion. Nothing here changes default behavior unless the user turns discovery on.

Demo videos (Desktop)

Rendered from the real DiscoveryNudge / DiscoverySettingsSection components with the production dark theme (captured headlessly; the container has no GUI/GTK for full Electron). The chat transcript around the nudge is a scripted re-enactment; the nudge, its install/auth flow, and the settings selector are the real components. The fully-live, end-to-end proof is the CLI run below.

① Full flow — prompt → response → discovery → install → calling the new tool (~44s):

Goose Server Card full-flow demo

https://storage.googleapis.com/remote-filesystem-tadas/screenshots/goose-discovery/goose-discovery-flow.mp4

A single session: you ask a question → Goose runs a fetch tool call that touches modelcontextprotocol.io → the sidecar surfaces its Server Card → one click installs it → a follow-up prompt immediately calls the new search_model_context_protocol · docs-server tool and gets the real Elicitation doc back.

② Component walkthrough — install, auth-entry, and the settings selector (~47s):

Goose Server Card discovery demo

https://storage.googleapis.com/remote-filesystem-tadas/screenshots/goose-discovery/goose-discovery-demo.mp4

How it works

tool call ──▶ DiscoveryService.observe_tool_call(name, args)   (non-blocking)
                 │  extract domains (mode-gated)
                 ▼
            resolver.resolve(domain)  ──▶  /.well-known/ai-catalog.json
                 │                          + referenced Server Cards / Skills
                 ▼
            pending discoveries ──▶  CLI inline nudge  /  Desktop nudge card
                 │  user clicks Install (enters auth if required)
                 ▼
            ExtensionConfig::StreamableHttp added to the live session
                 │
                 ▼
            new tools callable in the same session

Discovery modes (discovery_mode config key)

Mode Behavior
Off No background discovery. Goose never probes the domains it touches.
Light-touch (default) Probe only domains in fetch / search / browse tool calls. Conservative.
Full Probe every domain that appears in any tool call's arguments (broadest egress capture).

Exposed in both the CLI config and the Desktop Settings → Chat pane.

Surfaces

  • Corecrates/goose/src/discovery/ (DiscoveryService, Server Card / AI Catalog / Skills types, resolver + fixture layer, card→extension mapping with auth-header injection). Agent calls observe_tool_call per tool call.
  • goose-servercrates/goose-server/src/routes/discovery.rs: /discovery/pending, /discovery/install, /discovery/dismiss, /discovery/mode. install adds the extension to the live agent session and returns the now-callable tools.
  • CLIcrates/goose-cli/src/session/: inline terminal nudge between turns with a y/N install prompt and a post-install confirmation listing new tools.
  • Desktopui/desktop/src/components/discovery/DiscoveryNudge.tsx (rendered between the transcript and the prompt input) + settings/discovery/DiscoverySettingsSection.tsx.

Fixtures (clearly labeled fixture = true)

No public domain serves a real /.well-known/ai-catalog.json yet, so the cards the sidecar would fetch are hand-authored in crates/goose/src/discovery/fixtures.rs:

  • modelcontextprotocol.io → a minimal but REAL, auth-less Server Card for the live MCP server at https://modelcontextprotocol.io/mcp. The endpoint and its search_model_context_protocol tool are real — installing actually connects and the tool actually works (this is the headline demo). Plus a Skills bundle.
  • github.com / api.githubcopilot.com → a mocked GitHub MCP Server card (https://api.githubcopilot.com/mcp/, Authorization header auth) used to drive the auth-entry install flow.

Demo instructions (macOS)

All commands assume an inference provider. The screenshots/transcripts below used OpenRouter with a cheap tool-calling model. Set your own key — do not commit it:

export GOOSE_DISABLE_KEYRING=1
export GOOSE_PROVIDER=openrouter
export GOOSE_MODEL=openai/gpt-4o-mini
export OPENROUTER_API_KEY=sk-or-...     # your key

CLI

# build
cargo build -p goose-cli

# Full mode (probe every domain in any tool call)
goose configure          # Settings → discovery_mode → Full   (or: export GOOSE_DISCOVERY_MODE=full)
goose session

#   > Run this exact shell command and show me only its output:
#     curl -s -o /dev/null -w '%{http_code}' https://modelcontextprotocol.io/mcp
#   → the MCP SERVER DISCOVERED nudge appears; answer Yes to install.
#   > Now use the newly installed Model Context Protocol documentation search
#     tool to search for 'elicitation'. Report the title and url of the first result.
#   → search_model_context_protocol runs and returns the real Elicitation doc.

# Light-touch mode (probe only fetch/search/browse tool calls)
#   discovery_mode → Light-touch, then in a session:
#   > Use the web_scrape tool to fetch https://modelcontextprotocol.io and tell me the page title.
#   → same nudge → install → tool call works.

Desktop

cd ui/desktop
pnpm install
pnpm start            # or: pnpm run bundle:default for a packaged build
# Settings → Chat → "Server Card discovery" → Off / Light-touch / Full
# In a chat, run a tool call that touches modelcontextprotocol.io (e.g. fetch it).
# The discovery nudge appears between the transcript and the prompt box.
# Click Install → the server becomes active; ask it to search the MCP docs.
# For the GitHub card (auth demo): touch github.com → "auth required" → paste a
# token → Connect → the server is added with the Authorization header.

Verification

Desktop UI — screenshots of the real components

These are the real DiscoveryNudge / DiscoverySettingsSection React components, rendered with the production Tailwind theme (the exact dark-mode token palette injected by the app's applyThemeTokens) and representative fixture data, captured headlessly.

  • Discovery nudge — two discoveries surfaced (auth-less MCP server with Install, Skills bundle with View), in its real placement between the transcript and the prompt input:
    nudge cards
  • Installed into the session — green "mcp_docs is now active in this session" + the now-callable search_model_context_protocol tool:
    installed
  • Auth-entry flow (GitHub mocked card) — "auth required" badge, instructions, masked token field, button switches to Connect:
    github auth
  • Connected after auth — "github_mcp is now active in this session" + search_repositories, create_issue, get_pull_request:
    github connected
  • Settings selector — Off / Light-touch / Full (Full selected):
    settings

Desktop UI — component tests

  • vitest run over the discovery components — 4/4 passed: nudge renders the auth-less card + installs it (asserts install POST body + "active in this session" + tools); renders the GitHub auth card, reveals the token field on Install, sends the entered secret on Connect; renders nothing when there are no discoveries; settings reads discovery_mode and upserts on change.
    Test Files  2 passed (2)
         Tests  4 passed (4)
    

CLI — proven live, end-to-end (this is the headline)

Run live in a container against the real modelcontextprotocol.io/mcp endpoint with OpenRouter openai/gpt-4o-mini.

  • Full mode: a shell curl tool call → domain captured → MCP SERVER DISCOVERED nudge → Yes✓ installed docs-server is now active in this session → then search_model_context_protocol returns the real doc:
      ▸ shell
        command: curl -s -o /dev/null -w '%{http_code}' https://modelcontextprotocol.io/mcp
        405
    
      ✦ MCP SERVER DISCOVERED
      ┃  Model Context Protocol Docs
      ┃  source: modelcontextprotocol.io  (demo fixture)
    ◆  Install MCP server "Model Context Protocol Docs" into this session?  › Yes
      ✓ installed `docs-server` is now active in this session
        tools: docs-server__search_model_context_protocol, docs-server__query_docs_filesystem_model_context_protocol
    
      ▸ search_model_context_protocol docs-server
        query: elicitation
      **Title:** Elicitation
      **Link:** https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation
    
  • Light-touch mode: a web_scrape (fetch-like) tool call drives the same discover → install → search_model_context_protocol path. (A non-fetch shell call in light mode is correctly ignored — covered by a unit test.)

Core — Rust unit tests

  • cargo test -p goose discovery::7/7 passed, including:
    • github_fixture_requires_auth — the GitHub card requires auth, conversion without a secret errors, and with a secret injects Authorization: Bearer <token> into the StreamableHttp config.
    • fixture_resolver_returns_mcp_server_and_skills — auth-less MCP card + Skills bundle, endpoint https://modelcontextprotocol.io/mcp.
    • light_mode_ignores_non_fetch_tools — mode gating.
    • host-extraction tests (scheme-qualified, bare domain, www/port stripping).

Compilation

  • cargo check -p goose-server (discovery route + wiring) — clean.
  • goose-cli built and ran live (the CLI transcripts above are the proof).

CI status

All code-attributable checks are green on this branch:

  • Check Rust Code Format (cargo fmt --check) — pass
  • Lint Rust Code (cargo clippy -D warnings) — pass
  • Build and Test Rust Project — pass (includes the discovery unit tests)
  • Check MSRV, Check Generated Schemas, Test and Lint Electron Desktop App, Smoke Tests (Code Execution) — pass

Three integration jobs remain red — Compaction Tests, Smoke Tests, and goose server HTTP integration tests. These are not caused by this PR: each one makes a live LLM provider call and fails with 401 Unauthorized — x-api-key header is required because fork PRs don't have the ANTHROPIC_API_KEY secret. They exercise no discovery code and fail identically for any fork PR; they go green when run with provider secrets (upstream / on merge). The discovery feature's own behavior is fully covered by the green Build and Test Rust Project (Rust unit tests) and Test and Lint Electron Desktop App (vitest) jobs.

Caveats / notes

  • Experimental & opt-in. Default behavior is unchanged unless the user selects a discovery mode. Fixtures are labeled fixture = true and rendered with a "demo fixture" badge so demo content is never confused with a live, authenticated discovery.
  • Fixtures stand in for real catalogs because no public domain serves /.well-known/ai-catalog.json yet. The resolver is structured so a real HTTP resolver drops in behind the same interface.
  • GitHub card is mocked and its endpoint requires a real GitHub token, so a live GitHub tool call is out of scope for this demo — the card drives the auth-entry UX (proven by the Desktop screenshots and the github_fixture_requires_auth unit test). The auth-less MCP-docs path is the fully-live, end-to-end proof.
  • Desktop screenshots were captured via headless render of the real components (the dev container has no GUI/GTK for full Electron). They use the production Tailwind theme tokens; the components are unchanged from what the Electron app mounts. Component behavior is additionally locked down by the vitest suite.
  • The Desktop fixture labels in the screenshots (mcp_docs) are illustrative; the shipped Rust fixture names the extension docs-server (see the CLI transcript). Same flow, cosmetic naming only.

🤖 Generated with Claude Code

Goose Discovery Demo and others added 2 commits June 27, 2026 15:36
Experimental background "sidecar" that observes the domains a Goose session
touches, probes them for an AI Catalog (/.well-known/ai-catalog.json) and the
Server Cards / Skills bundles they reference, and surfaces a non-intrusive
one-click install nudge — without ever blocking the agent loop. Installing a
discovered MCP server (running auth if the card requires it) makes it active in
the SAME session.

Core (crates/goose/src/discovery):
- DiscoveryService sidecar with three modes (Off / Light-touch / Full) read from
  the `discovery_mode` config key. Light captures only fetch/search/browse tool
  calls; Full captures every domain in any tool call's arguments.
- Server Card + AI Catalog + Skills bundle types; resolver abstraction with a
  hand-authored fixture layer (no domain serves a real catalog yet).
- server_card_to_extension maps a card to an ExtensionConfig::StreamableHttp,
  injecting the card's auth header (e.g. `Authorization: Bearer <token>`).
- Agent observes each tool call (non-blocking) via discovery.observe_tool_call.

goose-server (crates/goose-server/src/routes/discovery.rs):
- /discovery/pending, /discovery/install, /discovery/dismiss, /discovery/mode.
  install adds the extension to the live agent session and returns the now-
  callable tools. A flat DiscoveryCard DTO decouples the UI from internal types.

CLI (crates/goose-cli/src/session):
- Inline terminal nudge rendered between turns, with a y/N install prompt and a
  post-install confirmation listing the new tools.

Desktop (ui/desktop):
- DiscoveryNudge rendered between the transcript and the prompt input: server
  card (Install) + Skills bundle (View) entries, auth-token entry for cards that
  require it, and an "active in this session" confirmation with the new tools.
- DiscoverySettingsSection: Off / Light-touch / Full selector in Chat settings.

Fixtures:
- modelcontextprotocol.io -> a minimal REAL, auth-less card for the live MCP
  server at https://modelcontextprotocol.io/mcp (search_model_context_protocol),
  plus a Skills bundle. Installing actually connects and the tool actually works.
- github.com -> mocked GitHub MCP server card (api.githubcopilot.com/mcp/,
  Authorization header) to drive the auth-entry install flow.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
…y CI

- cargo fmt across the discovery feature files (goose, goose-server, goose-cli)
- Box the Server variant of DiscoveryKind to fix clippy::large_enum_variant
  (ServerCard is much larger than SkillsBundle)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant