chore: Remove legacy MCP-UI proxy support (#10086)

This commit is contained in:
Lifei Zhou 2026-06-30 08:58:40 +10:00 committed by GitHub
parent fe7f16b727
commit f81a0e2471
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 62 additions and 1280 deletions

View file

@ -13,7 +13,6 @@ pub async fn check_token(
next: Next,
) -> Result<Response, StatusCode> {
if request.uri().path() == "/status"
|| request.uri().path() == "/mcp-ui-proxy"
|| request.uri().path() == "/mcp-app-proxy"
|| request.uri().path() == "/mcp-app-guest"
{

View file

@ -394,7 +394,6 @@ derive_utoipa!(IconTheme as IconThemeSchema);
super::routes::status::status,
super::routes::status::system_info,
super::routes::status::diagnostics,
super::routes::mcp_ui_proxy::mcp_ui_proxy,
super::routes::config_management::validate_config,
super::routes::config_management::upsert_config,
super::routes::config_management::remove_config,

View file

@ -1,53 +0,0 @@
use axum::{
extract::Query,
http::{header, StatusCode},
response::{Html, IntoResponse, Response},
routing::get,
Router,
};
use serde::Deserialize;
#[derive(Deserialize)]
struct ProxyQuery {
secret: String,
}
const MCP_UI_PROXY_HTML: &str = include_str!("templates/mcp_ui_proxy.html");
#[utoipa::path(
get,
path = "/mcp-ui-proxy",
params(
("secret" = String, Query, description = "Secret key for authentication")
),
responses(
(status = 200, description = "MCP UI proxy HTML page", content_type = "text/html"),
(status = 401, description = "Unauthorized - invalid or missing secret"),
)
)]
async fn mcp_ui_proxy(
axum::extract::State(secret_key): axum::extract::State<String>,
Query(params): Query<ProxyQuery>,
) -> Response {
if params.secret != secret_key {
return (StatusCode::UNAUTHORIZED, "Unauthorized").into_response();
}
(
[
(header::CONTENT_TYPE, "text/html; charset=utf-8"),
(
header::HeaderName::from_static("referrer-policy"),
"no-referrer",
),
],
Html(MCP_UI_PROXY_HTML),
)
.into_response()
}
pub fn routes(secret_key: String) -> Router {
Router::new()
.route("/mcp-ui-proxy", get(mcp_ui_proxy))
.with_state(secret_key)
}

View file

@ -6,7 +6,6 @@ pub mod errors;
#[cfg(feature = "local-inference")]
pub mod local_inference;
pub mod mcp_app_proxy;
pub mod mcp_ui_proxy;
pub mod prompts;
pub mod recipe;
pub mod recipe_utils;
@ -38,7 +37,6 @@ pub fn configure(state: Arc<crate::state::AppState>, secret_key: String) -> Rout
.merge(schedule::routes(state.clone()))
.merge(setup::routes(state.clone()))
.merge(telemetry::routes(state.clone()))
.merge(mcp_ui_proxy::routes(secret_key.clone()))
.merge(mcp_app_proxy::routes(secret_key))
.merge(session_events::routes(state.clone()))
.merge(sampling::routes(state.clone()))

View file

@ -1,143 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="referrer" content="no-referrer"/>
<!--
Permissive CSP so nested content is not constrained by top-level Goose Desktop CSP
- default-src: Fallback for other directives (allows same-origin)
- script-src: Allow scripts from any origin, inline, eval, wasm, and blob URLs
- style-src: Allow styles from any origin and inline styles
- font-src: Allow fonts from any origin
- img-src: Allow images from any origin
- connect-src: Allow network requests to any origin
- frame-src: Allow embedding iframes from any origin (required for proxy functionality)
- media-src: Allow audio/video media from any origin
- base-uri: Restrict <base> tag to same-origin only
- upgrade-insecure-requests: Automatically upgrade HTTP to HTTPS
-->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src * 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval' blob:; style-src * 'unsafe-inline'; font-src *; img-src *; connect-src *; frame-src *; media-src *; base-uri 'self'; upgrade-insecure-requests"/>
<title>MCP-UI Proxy</title>
<style>
body,
html {
margin: 0;
height: 100vh;
width: 100vw;
}
body {
display: flex;
flex-direction: column;
}
* {
box-sizing: border-box;
}
iframe {
background-color: transparent;
border: 0 none transparent;
padding: 0;
overflow: hidden;
flex-grow: 1;
}
</style>
</head>
<body>
<script>
const params = new URLSearchParams(location.search);
const contentType = params.get('contentType');
const target = params.get('url');
// Validate that the URL is a valid HTTP or HTTPS URL
function isValidHttpUrl(string) {
try {
const url = new URL(string);
return url.protocol === 'http:' || url.protocol === 'https:';
} catch (error) {
return false;
}
}
if (contentType === 'rawhtml') {
// Double-iframe raw HTML mode (HTML sent via postMessage)
const inner = document.createElement('iframe');
inner.style = 'width:100%; height:100%; border:none;';
// sandbox will be set from postMessage payload; default minimal before html arrives
inner.setAttribute('sandbox', 'allow-scripts');
document
.body
.appendChild(inner);
// Wait for HTML content from parent
window.addEventListener('message', (event) => {
if (event.source === window.parent) {
if (event.data && event.data.type === 'ui-html-content') {
const payload = event.data.payload || {};
const html = payload.html;
const sandbox = payload.sandbox;
if (typeof sandbox === 'string') {
inner.setAttribute('sandbox', sandbox);
}
if (typeof html === 'string') {
inner.srcdoc = html;
}
} else {
if (inner && inner.contentWindow) {
inner
.contentWindow
.postMessage(event.data, '*');
}
}
} else if (event.source === inner.contentWindow) {
// Relay messages from inner to parent
window
.parent
.postMessage(event.data, '*');
}
});
// Notify parent that proxy is ready to receive HTML (distinct event)
window
.parent
.postMessage({
type: 'ui-proxy-iframe-ready'
}, '*');
} else if (target) {
if (!isValidHttpUrl(target)) {
document.body.textContent = 'Error: invalid URL. Only HTTP and HTTPS URLs are allowed.';
} else {
const inner = document.createElement('iframe');
inner.src = target;
inner.style = 'width:100%; height:100%; border:none;';
// Default external URL sandbox; can be adjusted later by protocol if needed
inner.setAttribute('sandbox', 'allow-same-origin allow-scripts');
document
.body
.appendChild(inner);
const urlOrigin = new URL(target).origin;
window.addEventListener('message', (event) => {
if (event.source === window.parent) {
// listen for messages from the parent and send them to the iframe
if (inner.contentWindow) {
inner
.contentWindow
.postMessage(event.data, urlOrigin);
} else {
console.warn('[MCP-UI Proxy] iframe contentWindow is not available; message not sent');
}
} else if (event.source === inner.contentWindow) {
// listen for messages from the iframe and send them to the parent
window
.parent
.postMessage(event.data, '*');
}
});
}
} else {
document.body.textContent = 'Error: missing url or html parameter';
}
</script>
</body>
</html>

View file

@ -1,5 +1,5 @@
{
"label": "MCP Apps and MCP-UI",
"label": "MCP Apps",
"position": 55,
"link": {
"type": "doc",

View file

@ -1,43 +1,35 @@
---
title: Rich Interactive Chat with MCP Apps and MCP-UI
title: Rich Interactive Chat with MCP Apps
hide_title: true
description: Build interactive UI applications that render inside goose Desktop using MCP Apps and MCP-UI
description: Build interactive UI applications that render inside goose Desktop using MCP Apps
---
import Card from '@site/src/components/Card';
import styles from '@site/src/components/Card/styles.module.css';
import VideoCarousel from '@site/src/components/VideoCarousel';
import Card from "@site/src/components/Card";
import styles from "@site/src/components/Card/styles.module.css";
import VideoCarousel from "@site/src/components/VideoCarousel";
<h1 className={styles.pageTitle}>Rich Interactive Chat with MCP Apps and MCP-UI</h1>
<h1 className={styles.pageTitle}>Rich Interactive Chat with MCP Apps</h1>
<p className={styles.pageDescription}>
goose Desktop supports extensions that transform text-only responses into graphical, interactive experiences. Instead of reading through lists and descriptions, you can click, explore, and interact with UI components directly in your conversations.
goose Desktop supports extensions that transform text-only responses into
graphical, interactive experiences. Instead of reading through lists and
descriptions, you can click, explore, and interact with UI components directly
in your conversations.
</p>
<div className="video-container margin-bottom--lg">
<iframe
class="aspect-ratio"
src="https://www.youtube.com/embed/QJHGvsVXhjw"
title="Turn Any AI Chat Into an Interactive Experience | MCP-UI"
frameBorder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
<div className={styles.categorySection}>
<h2 className={styles.categoryTitle}>📚 Documentation & Guides</h2>
<div className={styles.cardGrid}>
<Card
<Card
title="Building MCP Apps"
description="Step-by-step tutorial to create interactive UI applications that render inside goose Desktop."
link="/docs/tutorials/building-mcp-apps"
/>
<Card
title="Using MCP Apps and MCP-UI"
description="goose transforms text-based responses into engaging graphical and interactive user experiences."
<Card
title="Using MCP Apps"
description="Launch standalone apps and render interactive UI directly in goose Desktop conversations."
link="/docs/guides/interactive-chat/mcp-ui"
/>
<Card
<Card
title="Auto Visualiser Extension"
description="Generate interactive data visualizations automatically in goose."
link="/docs/mcp/autovisualiser-mcp"
@ -45,74 +37,36 @@ import VideoCarousel from '@site/src/components/VideoCarousel';
</div>
</div>
<div className={styles.categorySection}>
<h2 className={styles.categoryTitle}>🎥 Videos</h2>
<VideoCarousel
id="mcp-apps-videos"
videos={[
{
type: "iframe",
src: "https://youtube.com/embed/QcojQ3Fwqsw",
title: "What are MCP Apps?",
description:
"What MCP Apps are and how they render interactive UIs in goose",
duration: "1:53",
},
]}
/>
</div>
<div className={styles.categorySection}>
<h2 className={styles.categoryTitle}>📝 Featured Blog Posts</h2>
<div className={styles.cardGrid}>
<Card
<Card
title="From MCP-UI to MCP Apps: Evolving Interactive Agent UIs"
description="A practical migration guide to MCP Apps: the 4 key changes, what broke, and why this shift matters."
link="/blog/2026/01/22/mcp-ui-to-mcp-apps"
/>
<Card
<Card
title="goose Lands MCP Apps"
description="goose ships early support for the draft MCP Apps specification, aligning with the emerging standard for interactive UIs."
link="/blog/2026/01/06/mcp-apps"
/>
<Card
title="MCP UI: Bringing the Browser into the Agent"
description="MCP-UI servers return content that goose Desktop renders as rich, embeddable UI."
link="/blog/2025/08/11/mcp-ui-post-browser-world"
/>
<Card
title="MCP-UI: The Future of Agentic Interfaces"
description="AI agents need to move beyond walls of text to rich and interactive UX."
link="/blog/2025/08/25/mcp-ui-future-agentic-interfaces"
/>
<Card
title="Auto Visualiser with MCP-UI"
description="Automatically render visual representations of data as you interact with it, powered by MCP-UI"
link="/blog/2025/08/27/autovisualiser-with-mcp-ui"
/>
<Card
title="How to Make An MCP Server MCP-UI Compatible"
description="Making an MCP server MCP-UI compatible requires just a few lines of code"
link="/blog/2025/09/08/turn-any-mcp-server-mcp-ui-compatible"
/>
<Card
title="Designing AI for Users, Not Just LLMs"
description="Building intent-based AI experiences with MCP-UI."
link="/blog/2025/10/14/designing-ai-for-humans"
/>
</div>
</div>
<div className={styles.categorySection}>
<h2 className={styles.categoryTitle}>🎥 More Videos</h2>
<VideoCarousel
id="more-videos"
videos={[
{
type: 'iframe',
src: 'https://youtube.com/embed/QcojQ3Fwqsw',
title: 'What are MCP Apps?',
description: 'Why MCP Apps are different from MCP-UI and what makes them portable, secure, and standardized',
duration: '1:53'
},
{
type: 'iframe',
src: 'https://youtube.com/embed/Kxj-vFBO_9U',
title: 'Building A MCP-UI Server',
description: 'Learn how to build your own MCP-UI server from scratch',
duration: '40:07'
},
{
type: 'iframe',
src: 'https://youtube.com/embed/GS-kmreZDgU',
title: 'Livestream - MCP-UI: The Future of Agentic Interfaces',
description: 'The goose team talks with MCP-UI creators about the future of visual interfaces',
duration: '55:32'
}
]}
/>
</div>

View file

@ -1,30 +1,24 @@
---
sidebar_position: 1
title: Using MCP Apps and MCP-UI
sidebar_label: Using MCP Apps and MCP-UI
description: Learn how goose renders interactive UI components from MCP Apps and MCP-UI extensions
title: Using MCP Apps
sidebar_label: Using MCP Apps
description: Learn how goose renders interactive UI components from MCP Apps extensions
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import GooseDesktopInstaller from '@site/src/components/GooseDesktopInstaller';
import CLIExtensionInstructions from '@site/src/components/CLIExtensionInstructions';
import { PanelLeft } from 'lucide-react';
# Using MCP Apps and MCP-UI
# Using MCP Apps
Extensions built with MCP Apps or MCP-UI allow goose Desktop to provide interactive and engaging user experiences. Instead of reading text responses and typing prompts, you can interact with a graphical and clickable UI.
Extensions built with MCP Apps allow goose Desktop to provide interactive and engaging user experiences. Instead of reading text responses and typing prompts, you can interact with a graphical and clickable UI.
:::info MCP Apps is the official specification
[MCP Apps](/docs/tutorials/building-mcp-apps) is now the official MCP specification for interactive UIs. MCP-UI extensions still work in goose, but MCP Apps is the recommended path for new extensions.
[MCP Apps](/docs/tutorials/building-mcp-apps) is the official MCP specification for interactive UIs. Use MCP Apps for new interactive extensions.
:::
:::warning Experimental Features
The features described in this topic are experimental and in active development. Behavior and support may change in future releases.
:::
## MCP Apps
MCP Apps bring interactive interfaces to goose through the official [MCP Apps specification](https://github.com/modelcontextprotocol/ext-apps). Depending on the extension, apps can be launched in standalone, sandboxed windows or embedded in your chat window.
### Launching Apps in Standalone Windows
@ -71,49 +65,6 @@ If needed, you can just ask goose whether the UI can be loaded in the chat windo
</video>
</div>
## MCP-UI
MCP-UI is an earlier specification for interactive UIs that renders content embedded in your chat. While MCP Apps is now the recommended approach, MCP-UI extensions continue to work in goose.
### Try It Out
See how interactive responses work in goose. For this exercise, we'll add an extension that connects to [MCP-UI Demos](https://mcp-aharvard.netlify.app/) provided by Andrew Harvard.
<Tabs groupId="interface">
<TabItem value="ui" label="goose Desktop" default>
<GooseDesktopInstaller
extensionId="richdemo"
extensionName="Rich Demo"
description="Demo interactive extension"
type="http"
url="https://mcp-aharvard.netlify.app/mcp"
/>
</TabItem>
<TabItem value="cli" label="goose CLI">
<CLIExtensionInstructions
name="rich_demo"
description="Demo interactive extension"
type="http"
url="https://mcp-aharvard.netlify.app/mcp"
timeout={300}
/>
</TabItem>
</Tabs>
In goose Desktop, ask:
- `Help me select seats for my flight`
Instead of just text, you'll see an interactive response with:
- A visual seat map with available and occupied seats
- Real-time, clickable selection capabilities
- A booking confirmation with flight details
Try out other demos:
- `Plan my next trip based on my mood`
- `What's the weather in Philadelphia?`
## For Extension Developers
Add interactivity to your own extensions:

View file

@ -9,7 +9,6 @@ import TabItem from '@theme/TabItem';
# Building Custom Extensions with goose
goose allows you to extend its functionality by creating your own custom extensions, which are built as MCP servers. These extensions are compatible with goose because it adheres to the [Model Context Protocol (MCP)][mcp-docs]. MCP is an open protocol that standardizes how applications provide context to LLMs. It enables a consistent way to connect LLMs to various data sources and tools, making it ideal for extending functionality in a structured and interoperable way. 
In this guide, we build an MCP server using the [Python SDK for MCP][mcp-python]. Well 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.
@ -105,7 +104,7 @@ def read_wikipedia_article(url: str) -> str:
# 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}")
@ -223,18 +222,18 @@ MCP Inspector requires Node.js and npm installed on your computer.
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. 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`
3. Enter `https://en.wikipedia.org/wiki/Bangladesh` for the URL and click `Run Tool`
[![MCP Inspector UI](../assets/guides/custom-extension-mcp-inspector.png)](../assets/guides/custom-extension-mcp-inspector.png)
@ -242,16 +241,16 @@ MCP Inspector requires Node.js and npm installed on your computer.
<TabItem value="cli" label="In the CLI">
1. Set up the project environment:
```bash
uv sync
```
```bash
uv sync
```
2. Activate your virtual environment:
```bash
source .venv/bin/activate
```
3. Install your project locally:
```bash
@ -275,6 +274,7 @@ MCP Inspector requires Node.js and npm installed on your computer.
options:
-h, --help show this help message and exit
```
</TabItem>
</Tabs>
@ -295,11 +295,13 @@ To add your MCP server as an extension in goose:
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
```
For example:
```plaintext
uv run /Users/smohammed/Development/mcp/mcp-wiki/.venv/bin/mcp-wiki
```
@ -341,12 +343,14 @@ goose supports advanced MCP features that can enhance your extensions.
**[MCP Sampling](/docs/guides/mcp-sampling)** allows your MCP servers to request AI completions from goose's LLM, transforming simple tools into intelligent agents.
**Key Benefits:**
- Your MCP server doesn't need its own OpenAI/Anthropic API key
- Tools can analyze data, provide explanations, and make intelligent decisions
- Enhanced user experience with smarter, more contextual responses
- Secure by design: requests are isolated and attributed automatically
**Getting Started:**
- Use the `sampling/createMessage` method in your MCP server to request AI assistance
- [goose's implementation](https://github.com/aaif-goose/goose/blob/main/crates/goose/src/agents/mcp_client.rs) currently supports text and image content types
- goose automatically advertises sampling capability to all MCP servers
@ -360,6 +364,7 @@ goose supports advanced MCP features that can enhance your extensions.
**[MCP Apps](/docs/tutorials/building-mcp-apps)** enable rich, interactive user interfaces instead of text-only responses.
**Key Benefits:**
- Return interactive UI components from your MCP server tools
- Components render securely in isolated sandboxes within goose Desktop
- Real-time user interactions trigger callbacks to your server
@ -368,10 +373,6 @@ goose supports advanced MCP features that can enhance your extensions.
**Learn More:** [Building MCP Apps Tutorial](/docs/tutorials/building-mcp-apps)
:::note
goose also supports [MCP-UI](/docs/guides/interactive-chat/mcp-ui), but MCP Apps is the recommended path for new extensions.
:::
[mcp-docs]: https://modelcontextprotocol.io/
[mcp-python]: https://github.com/modelcontextprotocol/python-sdk
[mcp-typescript]: https://github.com/modelcontextprotocol/typescript-sdk

View file

@ -2041,33 +2041,6 @@
}
}
},
"/mcp-ui-proxy": {
"get": {
"tags": [
"super::routes::mcp_ui_proxy"
],
"operationId": "mcp_ui_proxy",
"parameters": [
{
"name": "secret",
"in": "query",
"description": "Secret key for authentication",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "MCP UI proxy HTML page"
},
"401": {
"description": "Unauthorized - invalid or missing secret"
}
}
}
},
"/recipes/decode": {
"post": {
"tags": [

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -3343,32 +3343,6 @@ export type SyncFeaturedModelsResponses = {
200: unknown;
};
export type McpUiProxyData = {
body?: never;
path?: never;
query: {
/**
* Secret key for authentication
*/
secret: string;
};
url: '/mcp-ui-proxy';
};
export type McpUiProxyErrors = {
/**
* Unauthorized - invalid or missing secret
*/
401: unknown;
};
export type McpUiProxyResponses = {
/**
* MCP UI proxy HTML page
*/
200: unknown;
};
export type DecodeRecipeData = {
body: DecodeRecipeRequest;
path?: never;

View file

@ -261,7 +261,7 @@ export default function BaseChat({
}
}, [messages.length]);
// Listen for global scroll-to-bottom requests (e.g., from MCP UI prompt actions)
// Listen for global scroll-to-bottom requests (e.g., from MCP App message actions)
useEffect(() => {
const handleGlobalScrollRequest = () => {
// Add a small delay to ensure content has been rendered

View file

@ -1,401 +0,0 @@
import { AppEvents } from '../constants/events';
import {
UIResourceRenderer,
UIActionResultIntent,
UIActionResultLink,
UIActionResultNotification,
UIActionResultPrompt,
UIActionResultToolCall,
UIActionResult,
} from '@mcp-ui/client';
import { useState, useEffect } from 'react';
import { toast } from 'react-toastify';
import { EmbeddedResource } from '../api';
import { useTheme } from '../contexts/ThemeContext';
import { errorMessage } from '../utils/conversionUtils';
import { isProtocolSafe, getProtocol } from '../utils/urlSecurity';
import { defineMessages, useIntl } from '../i18n';
const i18n = defineMessages({
toastTitle: {
id: 'mcpUIResourceRenderer.toastTitle',
defaultMessage: 'MCP-UI {messageType} message',
},
toastMessageReceived: {
id: 'mcpUIResourceRenderer.toastMessageReceived',
defaultMessage: 'Message received for {message}.',
},
toastUnsupported: {
id: 'mcpUIResourceRenderer.toastUnsupported',
defaultMessage:
"Message received for {message}. {messageType} messages aren't supported yet, refer to console for more details.",
},
openExternalLinkTitle: {
id: 'mcpUIResourceRenderer.openExternalLinkTitle',
defaultMessage: 'Open External Link',
},
openProtocolLink: {
id: 'mcpUIResourceRenderer.openProtocolLink',
defaultMessage: 'Open {protocol} link?',
},
openLinkDetail: {
id: 'mcpUIResourceRenderer.openLinkDetail',
defaultMessage: 'This will open: {url}',
},
cancelButton: {
id: 'mcpUIResourceRenderer.cancelButton',
defaultMessage: 'Cancel',
},
openButton: {
id: 'mcpUIResourceRenderer.openButton',
defaultMessage: 'Open',
},
});
interface MCPUIResourceRendererProps {
content: EmbeddedResource & { type: 'resource' };
appendPromptToChat?: (value: string) => void;
}
// More specific result types using discriminated unions
type UIActionHandlerSuccess<T = unknown> = {
status: 'success';
data?: T;
message?: string;
};
type UIActionHandlerError = {
status: 'error';
error: {
code: UIActionErrorCode;
message: string;
details?: unknown;
};
};
type UIActionHandlerPending = {
status: 'pending';
message: string;
};
type UIActionHandlerResult<T = unknown> =
| UIActionHandlerSuccess<T>
| UIActionHandlerError
| UIActionHandlerPending;
// Strongly typed error codes
enum UIActionErrorCode {
UNSUPPORTED_ACTION = 'UNSUPPORTED_ACTION',
UNKNOWN_ACTION = 'UNKNOWN_ACTION',
TOOL_NOT_FOUND = 'TOOL_NOT_FOUND',
TOOL_EXECUTION_FAILED = 'TOOL_EXECUTION_FAILED',
NAVIGATION_FAILED = 'NAVIGATION_FAILED',
PROMPT_FAILED = 'PROMPT_FAILED',
INTENT_FAILED = 'INTENT_FAILED',
INVALID_PARAMS = 'INVALID_PARAMS',
NETWORK_ERROR = 'NETWORK_ERROR',
TIMEOUT = 'TIMEOUT',
}
// toast component
const ToastComponent = ({
messageType,
message,
isImplemented = true,
}: {
messageType: string;
message?: string;
isImplemented?: boolean;
}) => {
const intl = useIntl();
const title = intl.formatMessage(i18n.toastTitle, { messageType });
return (
<div className="flex flex-col gap-0 py-2 pr-4">
<p className="font-bold">{title}</p>
{isImplemented ? (
<p>
{intl.formatMessage(i18n.toastMessageReceived, {
message: <span className="font-bold">{message}</span>,
})}
</p>
) : (
<p>
{intl.formatMessage(i18n.toastUnsupported, {
message: <span className="font-bold">{message}</span>,
messageType: messageType.charAt(0).toUpperCase() + messageType.slice(1),
})}
</p>
)}
</div>
);
};
export default function MCPUIResourceRenderer({
content,
appendPromptToChat,
}: MCPUIResourceRendererProps) {
const intl = useIntl();
const { resolvedTheme } = useTheme();
const [proxyUrl, setProxyUrl] = useState<string | undefined>(undefined);
useEffect(() => {
const fetchProxyUrl = async () => {
try {
const gooseApiHost = await window.electron.getGoosedHostPort();
const secretKey = await window.electron.getSecretKey();
if (gooseApiHost && secretKey) {
setProxyUrl(`${gooseApiHost}/mcp-ui-proxy?secret=${encodeURIComponent(secretKey)}`);
} else {
console.error('Failed to get goosed host/port or secret key');
}
} catch (error) {
console.error('Error fetching MCP-UI Proxy URL:', error);
}
};
fetchProxyUrl().catch(console.error);
}, []);
const handleUIAction = async (actionEvent: UIActionResult): Promise<UIActionHandlerResult> => {
// result to pass back to the MCP-UI
let result: UIActionHandlerResult;
const handleToolAction = async (
actionEvent: UIActionResultToolCall
): Promise<UIActionHandlerResult> => {
const { toolName, params } = actionEvent.payload;
toast.info(<ToastComponent messageType="tool" message={toolName} isImplemented={false} />, {
theme: resolvedTheme,
});
return {
status: 'error' as const,
error: {
code: UIActionErrorCode.UNSUPPORTED_ACTION,
message: 'Tool calls are not yet implemented',
details: { toolName, params },
},
};
};
const handlePromptAction = async (
actionEvent: UIActionResultPrompt
): Promise<UIActionHandlerResult> => {
const { prompt } = actionEvent.payload;
if (appendPromptToChat) {
try {
appendPromptToChat(prompt);
window.dispatchEvent(new CustomEvent(AppEvents.SCROLL_CHAT_TO_BOTTOM));
return {
status: 'success' as const,
message: 'Prompt sent to chat successfully',
};
} catch (error) {
return {
status: 'error' as const,
error: {
code: UIActionErrorCode.PROMPT_FAILED,
message: 'Failed to send prompt to chat',
details: errorMessage(error),
},
};
}
}
return {
status: 'error' as const,
error: {
code: UIActionErrorCode.UNSUPPORTED_ACTION,
message: 'Prompt handling is not implemented - append prop is required',
details: { prompt },
},
};
};
const handleLinkAction = async (
actionEvent: UIActionResultLink
): Promise<UIActionHandlerResult> => {
const { url } = actionEvent.payload;
try {
// Safe protocols open directly, unknown protocols require user confirmation
// Dangerous protocols are blocked by main.ts in the open-external handler
if (isProtocolSafe(url)) {
await window.electron.openExternal(url);
return {
status: 'success' as const,
message: `Opened ${url} in default application`,
};
}
// Unknown protocols require user confirmation
const protocol = getProtocol(url);
if (!protocol) {
return {
status: 'error' as const,
error: {
code: UIActionErrorCode.INVALID_PARAMS,
message: `Invalid URL format: ${url}`,
details: { url },
},
};
}
const result = await window.electron.showMessageBox({
type: 'question',
buttons: [
intl.formatMessage(i18n.cancelButton),
intl.formatMessage(i18n.openButton),
],
defaultId: 0,
title: intl.formatMessage(i18n.openExternalLinkTitle),
message: intl.formatMessage(i18n.openProtocolLink, { protocol }),
detail: intl.formatMessage(i18n.openLinkDetail, { url }),
});
if (result.response !== 1) {
return {
status: 'error' as const,
error: {
code: UIActionErrorCode.NAVIGATION_FAILED,
message: 'User cancelled',
details: { url },
},
};
}
await window.electron.openExternal(url);
return {
status: 'success' as const,
message: `Opened ${url} in default application`,
};
} catch (error) {
return {
status: 'error' as const,
error: {
code: UIActionErrorCode.NAVIGATION_FAILED,
message: `Failed to open URL: ${url}`,
details: errorMessage(error),
},
};
}
};
const handleNotifyAction = async (
actionEvent: UIActionResultNotification
): Promise<UIActionHandlerResult> => {
const { message } = actionEvent.payload;
toast.info(<ToastComponent messageType="notify" message={message} isImplemented={true} />, {
theme: resolvedTheme,
});
return {
status: 'success' as const,
data: {
displayedAt: new Date().toISOString(),
message: 'Notification displayed',
details: actionEvent.payload,
},
};
};
const handleIntentAction = async (
actionEvent: UIActionResultIntent
): Promise<UIActionHandlerResult> => {
toast.info(
<ToastComponent
messageType="intent"
message={actionEvent.payload.intent}
isImplemented={false}
/>,
{
theme: resolvedTheme,
}
);
return {
status: 'error' as const,
error: {
code: UIActionErrorCode.UNSUPPORTED_ACTION,
message: 'Intent handling is not yet implemented',
details: actionEvent.payload,
},
};
};
try {
switch (actionEvent.type) {
case 'tool':
result = await handleToolAction(actionEvent);
break;
case 'prompt':
result = await handlePromptAction(actionEvent);
break;
case 'link':
result = await handleLinkAction(actionEvent);
break;
case 'notify':
result = await handleNotifyAction(actionEvent);
break;
case 'intent':
result = await handleIntentAction(actionEvent);
break;
default: {
const _exhaustiveCheck: never = actionEvent;
console.error('Unhandled MCP-UI action type:', _exhaustiveCheck);
result = {
status: 'error',
error: {
code: UIActionErrorCode.UNKNOWN_ACTION,
message: `Unknown action type`,
details: actionEvent,
},
};
}
}
} catch (error) {
console.error('Unexpected error handling MCP-UI action:', error);
result = {
status: 'error',
error: {
code: UIActionErrorCode.UNKNOWN_ACTION,
message: 'An unexpected error occurred',
details: error instanceof Error ? error.stack : error,
},
};
}
return result;
};
return (
<div className="mt-3 p-4 border border-border-primary rounded-lg bg-background-secondary">
<div className="overflow-hidden rounded-sm">
<UIResourceRenderer
resource={content.resource}
onUIAction={handleUIAction}
supportedContentTypes={['rawHtml', 'externalUrl']} // Goose does not support remoteDom content
htmlProps={{
autoResizeIframe: {
height: true,
width: false, // set to false to allow for responsive design
},
iframeRenderData: {
// iframeRenderData allows us to pass data down to MCP-UIs
// MCP-UIs might find stuff like host and theme for conditional rendering
// usage of this is experimental, leaving in place for demos
host: 'goose',
theme: resolvedTheme,
},
proxy: proxyUrl, // refer to https://mcpui.dev/guide/client/using-a-proxy
}}
/>
</div>
</div>
);
}

View file

@ -13,22 +13,16 @@ import {
} from '../types/message';
import { cn, snakeToTitleCase } from '../utils';
import { LoadingStatus } from './ui/Dot';
import { ChevronRight, ExternalLink, FlaskConical } from 'lucide-react';
import { ChevronRight, ExternalLink } from 'lucide-react';
import { TooltipWrapper } from './settings/providers/subcomponents/buttons/TooltipWrapper';
import MCPUIResourceRenderer from './MCPUIResourceRenderer';
import { isUIResource } from '@mcp-ui/client';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import type { ContentBlock, EmbeddedResource } from '../api';
import type { ContentBlock } from '../api';
import McpAppRenderer from './McpApps/McpAppRenderer';
import ToolApprovalButtons from './ToolApprovalButtons';
import { defineMessages, useIntl } from '../i18n';
const i18n = defineMessages({
mcpUiExperimental: {
id: 'toolCallWithResponse.mcpUiExperimental',
defaultMessage: 'MCP UI is experimental and may change at any time.',
},
viewSubagentSession: {
id: 'toolCallWithResponse.viewSubagentSession',
defaultMessage: 'View subagent session',
@ -152,13 +146,6 @@ function getToolResultContent(toolResult: Record<string, unknown>): ContentBlock
});
}
function isEmbeddedResource(
content: ContentBlock
): content is EmbeddedResource & { type: 'resource' } {
const c = content as Record<string, unknown>;
return c.type === 'resource' && typeof c.resource === 'object' && c.resource !== null;
}
interface McpAppWrapperProps {
toolRequest: ToolRequestMessageContent;
toolResponse?: ToolResponseMessageContent;
@ -236,7 +223,6 @@ export default function ToolCallWithResponse({
confirmationContent,
isApprovalClicked,
}: ToolCallWithResponseProps) {
const intl = useIntl();
// Handle both the wrapped ToolResult format and the unwrapped format
// The server serializes ToolResult<T> as { status: "success", value: T } or { status: "error", error: string }
const toolCallData = toolRequest.toolCall as Record<string, unknown>;
@ -298,28 +284,6 @@ export default function ToolCallWithResponse({
</div>
)}
</div>
{/* MCP UI — Inline */}
{shouldShowMcpContent &&
!hasMcpAppResourceURI &&
toolResponse?.toolResult &&
getToolResultContent(toolResponse.toolResult).map((content, index) => {
if (!isEmbeddedResource(content)) return null;
if (isUIResource(content)) {
return (
<div key={index} className="mt-3">
<MCPUIResourceRenderer content={content} appendPromptToChat={append} />
<div className="mt-3 p-4 py-3 border border-border-primary rounded-lg bg-background-secondary flex items-center">
<FlaskConical className="mr-2" size={20} />
<div className="text-sm font-sans">
{intl.formatMessage(i18n.mcpUiExperimental)}
</div>
</div>
</div>
);
} else {
return null;
}
})}
{/* MCP App */}
{shouldShowMcpContent && hasMcpAppResourceURI && sessionId && (

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "Wiedergabe im Bild-in-Bild-Modus"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "Abbrechen"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "Öffnen"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "Externen Link öffnen"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "Dies öffnet: {url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "{protocol}-Link öffnen?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "Nachricht für {message} empfangen."
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "MCP-UI-Nachricht {messageType}"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "Nachricht für {message} empfangen. Nachrichten vom Typ {messageType} werden noch nicht unterstützt, weitere Details finden Sie in der Konsole."
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{# Element gefunden} other{# Elemente gefunden}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "Protokolle"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "MCP UI ist experimentell und kann sich jederzeit ändern."
},
"toolCallWithResponse.output": {
"defaultMessage": "Ausgabe"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "Playing in Picture-in-Picture"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "Cancel"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "Open"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "Open External Link"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "This will open: {url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "Open {protocol} link?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "Message received for {message}."
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "MCP-UI {messageType} message"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "Message received for {message}. {messageType} messages aren't supported yet, refer to console for more details."
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{# item found} other{# items found}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "Logs"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "MCP UI is experimental and may change at any time."
},
"toolCallWithResponse.output": {
"defaultMessage": "Output"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "Reproduciendo en imagen en imagen"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "Cancelar"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "Abrir"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "Abrir enlace externo"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "Esto abrirá: {url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "¿Abrir enlace {protocol}?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "Mensaje recibido para {message}."
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "Mensaje {messageType} de MCP-UI"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "Mensaje recibido para {message}. Los mensajes {messageType} aún no se admiten, consulta la consola para más detalles."
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{# elemento encontrado} other{# elementos encontrados}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "Logs"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "La UI de MCP es experimental y puede cambiar en cualquier momento."
},
"toolCallWithResponse.output": {
"defaultMessage": "Salida"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "Lecture en Picture-in-Picture"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "Annuler"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "Ouvrir"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "Ouvrir le lien externe"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "Cela ouvrira : {url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "Ouvrir le lien {protocol} ?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "Message reçu pour {message}."
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "Message MCP-UI {messageType}"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "Message reçu pour {message}. Les messages {messageType} ne sont pas encore pris en charge, consultez la console pour plus de détails."
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{# élément trouvé} other{# éléments trouvés}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "Journaux"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "L'interface MCP est expérimentale et peut changer à tout moment."
},
"toolCallWithResponse.output": {
"defaultMessage": "Sortie"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "पिक्चर-इन-पिक्चर में बजाना"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "रद्द करें"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "खुला"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "बाहरी लिंक खोलें"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "यह खुलेगा: {url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "{protocol} लिंक खोलें?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "{message} के लिए संदेश प्राप्त हुआ।"
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "एमसीपी-यूआई {messageType} संदेश"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "{message} के लिए संदेश प्राप्त हुआ। {messageType} संदेश अभी तक समर्थित नहीं हैं, अधिक विवरण के लिए कंसोल देखें।"
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{# आइटम मिला} other{# आइटम मिले}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "लॉग"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "MCP यूआई प्रयोगात्मक है और किसी भी समय बदल सकता है।"
},
"toolCallWithResponse.output": {
"defaultMessage": "आउटपुट"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "Memutar dalam Picture-in-Picture"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "Batal"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "Buka"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "Buka Tautan Eksternal"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "Ini akan membuka: {url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "Buka tautan {protocol}?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "Pesan diterima untuk {message}."
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "Pesan MCP-UI {messageType}"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "Pesan diterima untuk {message}. Pesan {messageType} belum didukung, lihat konsol untuk detail lebih lanjut."
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{# item ditemukan} other{# item ditemukan}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "Log"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "MCP UI bersifat eksperimental dan dapat berubah sewaktu-waktu."
},
"toolCallWithResponse.output": {
"defaultMessage": "Keluaran"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "In riproduzione in Picture-in-Picture"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "Annulla"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "Apri"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "Apri link esterno"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "Questo aprirà: {url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "Aprire il link {protocol}?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "Messaggio ricevuto per {message}."
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "Messaggio MCP-UI {messageType}"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "Messaggio ricevuto per {message}. I messaggi {messageType} non sono ancora supportati, consulta la console per maggiori dettagli."
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{# elemento trovato} other{# elementi trovati}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "Log"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "L'interfaccia MCP è sperimentale e può cambiare in qualsiasi momento."
},
"toolCallWithResponse.output": {
"defaultMessage": "Output"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "ピクチャ・イン・ピクチャで再生中"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "キャンセル"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "開く"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "外部リンクを開く"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "次を開きます: {url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "{protocol}リンクを開きますか?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "{message}のメッセージを受信しました。"
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "MCP-UI {messageType}メッセージ"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "{message}のメッセージを受信しました。{messageType}メッセージはまだサポートされていません。詳細はコンソールを確認してください。"
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{# 件見つかりました} other{# 件見つかりました}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "ログ"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "MCP UIは実験的な機能であり、予告なく変更される場合があります。"
},
"toolCallWithResponse.output": {
"defaultMessage": "出力"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "PIP(Picture-in-Picture)에서 재생 중"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "취소"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "열기"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "외부 링크 열기"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "열립니다: {url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "{protocol} 링크를 열까요?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "{message}에 대한 메시지가 수신되었습니다."
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "MCP-UI {messageType} 메시지"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "{message}에 대한 메시지가 수신되었습니다. {messageType} 메시지는 아직 지원되지 않습니다. 자세한 내용은 콘솔을 참조하세요."
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{#개 항목 발견} other{#개 항목 발견}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "로그"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "MCP UI는 실험적이며 언제든지 변경될 수 있습니다."
},
"toolCallWithResponse.output": {
"defaultMessage": "출력"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "Bermain dalam Gambar-dalam-Gambar"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "Batal"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "Buka"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "Buka Pautan Luaran"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "Ini akan membuka: {url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "Buka pautan {protocol}?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "Mesej diterima untuk {message}."
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "Mesej MCP-UI {messageType}"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "Mesej diterima untuk {message}. Mesej {messageType} belum disokong lagi, rujuk konsol untuk butiran lanjut."
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{# item dijumpai} other{# item dijumpai}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "Log"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "MCP UI adalah eksperimen dan boleh berubah pada bila-bila masa."
},
"toolCallWithResponse.output": {
"defaultMessage": "Output"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "Reproduzindo em Picture-in-Picture"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "Cancelar"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "Abrir"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "Abrir Link Externo"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "Isto abrirá: {url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "Abrir link {protocol}?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "Mensagem recebida para {message}."
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "Mensagem {messageType} do MCP-UI"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "Mensagem recebida para {message}. As mensagens {messageType} ainda não são suportadas, consulte o console para mais detalhes."
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{# item encontrado} other{# itens encontrados}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "Registos"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "A IU do MCP é experimental e pode mudar a qualquer momento."
},
"toolCallWithResponse.output": {
"defaultMessage": "Saída"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "Воспроизведение в режиме «картинка в картинке»"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "Отмена"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "Открыть"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "Открыть внешнюю ссылку"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "Будет открыто: {url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "Открыть ссылку {protocol}?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "Получено сообщение для {message}."
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "Сообщение MCP-UI {messageType}"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "Получено сообщение для {message}. Сообщения {messageType} пока не поддерживаются; подробности смотрите в консоли."
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count, plural, one {Найден # элемент} few {Найдено # элемента} many {Найдено # элементов} other {Найдено # элемента}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "Журналы"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "MCP UI является экспериментальным и может измениться в любой момент."
},
"toolCallWithResponse.output": {
"defaultMessage": "Вывод"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "Resim İçinde Resim'de Oynatma"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "İptal"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "Açık"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "Dış Bağlantıyı Aç"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "Bu açılacak: {url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "{protocol} bağlantısıılsın mı?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "{message} için mesaj alındı."
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "MCP-UI {messageType} mesajı"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "{message} için mesaj alındı. {messageType} mesajları henüz desteklenmiyor; daha fazla ayrıntı için konsola bakın."
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{# öğe bulundu} other{# öğe bulundu}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "Günlükler"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "MCP Kullanıcı arayüzü deneyseldir ve herhangi bir zamanda değişebilir."
},
"toolCallWithResponse.output": {
"defaultMessage": ıkış"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "Đang phát ở chế độ Hình-trong-hình"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "Hủy"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "Mở"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "Mở liên kết bên ngoài"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "Thao tác này sẽ mở: {url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "Mở liên kết {protocol}?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "Đã nhận thông báo cho {message}."
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "Thông báo MCP-UI {messageType}"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "Đã nhận thông báo cho {message}. Thông báo {messageType} chưa được hỗ trợ, hãy tham khảo console để biết thêm chi tiết."
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{# mục được tìm thấy} other{# mục được tìm thấy}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "Nhật ký"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "MCP UI là thử nghiệm và có thể thay đổi bất kỳ lúc nào."
},
"toolCallWithResponse.output": {
"defaultMessage": "Đầu ra"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "正在以画中画播放"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "取消"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "打开"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "打开外部链接"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "这将打开:{url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "打开 {protocol} 链接?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "已收到 {message} 的消息。"
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "MCP-UI {messageType} 消息"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "已收到 {message} 的消息。目前尚不支持 {messageType} 类型的消息,详见控制台。"
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{找到 # 项} other{找到 # 项}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "日志"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "MCP UI 是实验性功能,随时可能更改。"
},
"toolCallWithResponse.output": {
"defaultMessage": "输出"
},

View file

@ -1949,30 +1949,6 @@
"mcpAppRenderer.playingInPip": {
"defaultMessage": "正在子母畫面中播放"
},
"mcpUIResourceRenderer.cancelButton": {
"defaultMessage": "取消"
},
"mcpUIResourceRenderer.openButton": {
"defaultMessage": "開啟"
},
"mcpUIResourceRenderer.openExternalLinkTitle": {
"defaultMessage": "開啟外部連結"
},
"mcpUIResourceRenderer.openLinkDetail": {
"defaultMessage": "這會開啟:{url}"
},
"mcpUIResourceRenderer.openProtocolLink": {
"defaultMessage": "要開啟 {protocol} 連結嗎?"
},
"mcpUIResourceRenderer.toastMessageReceived": {
"defaultMessage": "已收到 {message} 的訊息。"
},
"mcpUIResourceRenderer.toastTitle": {
"defaultMessage": "MCP-UI {messageType} 訊息"
},
"mcpUIResourceRenderer.toastUnsupported": {
"defaultMessage": "已收到 {message} 的訊息。目前尚不支援 {messageType} 訊息,詳情請參閱主控台。"
},
"mentionPopover.itemsFound": {
"defaultMessage": "{count,plural,one{找到 # 個項目} other{找到 # 個項目}}"
},
@ -4430,9 +4406,6 @@
"toolCallWithResponse.logs": {
"defaultMessage": "記錄"
},
"toolCallWithResponse.mcpUiExperimental": {
"defaultMessage": "MCP UI 為實驗性功能,可能隨時變更。"
},
"toolCallWithResponse.output": {
"defaultMessage": "輸出"
},