Skip to content

Filter Apps page to only show standalone Goose Apps#6811

Merged
DOsinga merged 3 commits intomainfrom
aharvard/filter-apps-page-by-goose-app-metadata
Feb 13, 2026
Merged

Filter Apps page to only show standalone Goose Apps#6811
DOsinga merged 3 commits intomainfrom
aharvard/filter-apps-page-by-goose-app-metadata

Conversation

@aharvard
Copy link
Collaborator

@aharvard aharvard commented Jan 29, 2026

Summary

Filter the Apps page to only display apps from the "apps" extension (vibe coded apps built by Goose).

Changes

  • Client-side only change in AppsView.tsx
  • Filters apps using mcpServers?.includes('apps') - the same pattern already used for the "Custom app" label
  • No backend changes required

Why

Previously, the Apps page displayed all MCP UI resources from all extensions, including chat-dependent MCP apps that require a chat context to function. This caused confusion since those apps cannot be launched standalone.

Now only apps created by Goose (vibe coded apps) are shown on the Apps page.

Previously, the Apps page displayed all MCP UI resources from all extensions,
including chat-dependent MCP apps that require a chat context to function.
This caused confusion since those apps cannot be launched standalone.

This change introduces an 'is_goose_app' field to the GooseApp struct that
identifies true standalone Goose Apps by checking for JSON-LD metadata with
@type: "GooseApp" in the HTML content. The list_apps API now filters to
only return apps with this flag set.

Changes:
- Add is_goose_app field to GooseApp struct
- Add has_goose_app_metadata() helper to detect Goose App JSON-LD metadata
- Set is_goose_app in from_html(), fetch_mcp_apps(), and handle_create_app()
- Filter by is_goose_app in list_apps route handler
- Regenerate OpenAPI spec and TypeScript types
Copilot AI review requested due to automatic review settings January 29, 2026 15:01
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Filters the Apps page to only show standalone goose apps by adding an explicit is_goose_app flag derived from Goose App JSON-LD metadata and applying that filter in the server’s list_apps endpoint.

Changes:

  • Added is_goose_app to GooseApp and logic to detect Goose App JSON-LD metadata.
  • Marked newly created apps as standalone (is_goose_app: true) and filtered /agent/list_apps results accordingly.
  • Regenerated OpenAPI + TypeScript client types to expose isGooseApp.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
ui/desktop/src/api/types.gen.ts Adds isGooseApp to generated TS types for app filtering.
ui/desktop/openapi.json Adds isGooseApp to the OpenAPI schema for GooseApp.
crates/goose/src/goose_apps/app.rs Adds is_goose_app to GooseApp and implements metadata detection.
crates/goose/src/agents/apps_extension.rs Marks newly created apps as standalone (is_goose_app: true).
crates/goose-server/src/routes/agent.rs Filters list_apps results to only return is_goose_app apps and updates caching behavior.
Comments suppressed due to low confidence (1)

crates/goose-server/src/routes/agent.rs:1023

  • Cache cleanup now derives active_extensions from the filtered apps list, so extensions that only provide non-Goose apps will never have their cached entries deleted, causing stale/unused cache files to accumulate; compute the set from all_apps (or from ui_resources) and then store only the filtered subset.
    if let Some(cache) = cache.as_ref() {
        let active_extensions: HashSet<String> = apps
            .iter()
            .flat_map(|app| app.mcp_servers.iter().cloned())
            .collect();

        for extension_name in active_extensions {
            if let Err(e) = cache.delete_extension_apps(&extension_name) {
                warn!(

Comment on lines 49 to 55
let metadata_re = match Regex::new(&format!(
r#"(?s)<script type="{}"[^>]*>\s*(.*?)\s*</script>"#,
regex::escape(Self::METADATA_SCRIPT_TYPE)
)) {
Ok(re) => re,
Err(_) => return false,
};
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This recompiles the same regex every time has_goose_app_metadata is called (likely once per UI resource on each /list_apps), which is unnecessarily expensive; consider a precompiled static (e.g., once_cell::sync::Lazy<Regex>) or reusing the existing regex from from_html.

Copilot uses AI. Check for mistakes.
Comment on lines 57 to 68
let Some(json_str) = metadata_re.captures(html).and_then(|cap| cap.get(1)) else {
return false;
};

let Ok(metadata) = serde_json::from_str::<serde_json::Value>(json_str.as_str()) else {
return false;
};

metadata
.get("@type")
.and_then(|v| v.as_str())
.is_some_and(|t| t == Self::GOOSE_APP_TYPE)
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has_goose_app_metadata only inspects the first application/ld+json <script> match, so it can return false even if a later JSON-LD block contains @type: "GooseApp"; iterate over all matches and return true if any block qualifies.

Suggested change
let Some(json_str) = metadata_re.captures(html).and_then(|cap| cap.get(1)) else {
return false;
};
let Ok(metadata) = serde_json::from_str::<serde_json::Value>(json_str.as_str()) else {
return false;
};
metadata
.get("@type")
.and_then(|v| v.as_str())
.is_some_and(|t| t == Self::GOOSE_APP_TYPE)
for caps in metadata_re.captures_iter(html) {
let Some(json_match) = caps.get(1) else {
continue;
};
let Ok(metadata) =
serde_json::from_str::<serde_json::Value>(json_match.as_str())
else {
continue;
};
if metadata
.get("@type")
.and_then(|v| v.as_str())
.is_some_and(|t| t == Self::GOOSE_APP_TYPE)
{
return true;
}
}
false

Copilot uses AI. Check for mistakes.
@aharvard aharvard requested a review from DOsinga January 29, 2026 15:05
@DOsinga
Copy link
Collaborator

DOsinga commented Jan 29, 2026

as discussed, I think we should do the filtering on the client

Filter the Apps page to only display apps from the 'apps' extension
(vibe coded apps built by Goose). This is a client-side only change
that uses the existing mcpServers field to identify custom apps.

No backend changes required - uses existing mcpServers.includes('apps')
pattern that was already used for the 'Custom app' label.
Copilot AI review requested due to automatic review settings January 29, 2026 21:06
@aharvard
Copy link
Collaborator Author

@DOsinga, we're client-side only now. LMK if this is cool.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Comment on lines +38 to +39
// Only show apps from the "apps" extension (vibe coded apps built by Goose)
setApps(cachedApps.filter((a) => a.mcpServers?.includes('apps')));
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This filtering uses a.mcpServers?.includes('apps'), but the PR description says the Apps page should filter on app.isGooseApp; either update the implementation to use the isGooseApp field (and ensure it exists in the generated GooseApp type) or update the PR description/title to match the actual filter criterion.

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +62
// Only show apps from the "apps" extension (vibe coded apps built by Goose)
setApps(freshApps.filter((a) => a.mcpServers?.includes('apps')));
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same apps.filter((a) => a.mcpServers?.includes('apps')) logic is duplicated in several places; factor this into a small helper (and/or a shared constant for the extension name) to avoid missing an update if the filter rule changes again.

Copilot uses AI. Check for mistakes.
@aharvard
Copy link
Collaborator Author

aharvard commented Feb 4, 2026

@DOsinga, friendly ping for when you have a moment

@DOsinga DOsinga added this pull request to the merge queue Feb 13, 2026
Merged via the queue into main with commit 0e9f513 Feb 13, 2026
27 checks passed
@DOsinga DOsinga deleted the aharvard/filter-apps-page-by-goose-app-metadata branch February 13, 2026 11:45
jh-block added a commit that referenced this pull request Feb 13, 2026
* origin/main:
  fix: allow concurrent tool execution within the same MCP extension (#7202)
  fix: handle missing arguments in MCP tool calls to prevent GUI crash (#7143)
  Filter Apps page to only show standalone Goose Apps (#6811)
  opt: use static for Regex (#7205)
katzdave added a commit that referenced this pull request Feb 13, 2026
…ntext

* 'main' of github.com:block/goose:
  feat: add onFallbackRequest handler to McpAppRenderer (#7208)
  feat: add streaming support for Claude Code CLI provider (#6833)
  fix: The detected filetype is PLAIN_TEXT, but the provided filetype was HTML (#6885)
  Add prompts (#7212)
  Add testing instructions for speech to text (#7185)
  Diagnostic files copying (#7209)
  fix: allow concurrent tool execution within the same MCP extension (#7202)
  fix: handle missing arguments in MCP tool calls to prevent GUI crash (#7143)
  Filter Apps page to only show standalone Goose Apps (#6811)
  opt: use static for Regex (#7205)
  nit: show dir in title, and less... jank (#7138)
  feat(gemini-cli): use stream-json output and re-use session (#7118)
  chore(deps): bump qs from 6.14.1 to 6.14.2 in /documentation (#7191)
  Switch jsonwebtoken to use aws-lc-rs (already used by rustls) (#7189)
  chore(deps): bump qs from 6.14.1 to 6.14.2 in /evals/open-model-gym/mcp-harness (#7184)
  Add SLSA build provenance attestations to release workflows (#7097)
  fix save and run recipe not working (#7186)
  Upgraded npm packages for latest security updates (#7183)
  docs: reasoning effort levels for Codex provider (#6798)
michaelneale added a commit that referenced this pull request Feb 16, 2026
* origin/main: (42 commits)
  fix: use dynamic port for Tetrate auth callback server (#7228)
  docs: removing LLM Usage admonitions (#7227)
  feat(otel): respect standard OTel env vars for exporter selection (#7144)
  fix: fork session (#7219)
  Bump version numbers for 1.24.0 release (#7214)
  Move platform extensions into their own folder (#7210)
  fix: ignore deprecated skills extension (#7139)
  Add a goosed over HTTP integration test, and test the developer tool PATH (#7178)
  feat: add onFallbackRequest handler to McpAppRenderer (#7208)
  feat: add streaming support for Claude Code CLI provider (#6833)
  fix: The detected filetype is PLAIN_TEXT, but the provided filetype was HTML (#6885)
  Add prompts (#7212)
  Add testing instructions for speech to text (#7185)
  Diagnostic files copying (#7209)
  fix: allow concurrent tool execution within the same MCP extension (#7202)
  fix: handle missing arguments in MCP tool calls to prevent GUI crash (#7143)
  Filter Apps page to only show standalone Goose Apps (#6811)
  opt: use static for Regex (#7205)
  nit: show dir in title, and less... jank (#7138)
  feat(gemini-cli): use stream-json output and re-use session (#7118)
  ...
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.

2 participants

Comments