mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
@@ -15,7 +15,7 @@
|
||||
* - "standalone" — Goose-specific mode for dedicated Electron windows
|
||||
*/
|
||||
|
||||
import { AppRenderer } from '@mcp-ui/client';
|
||||
import { AppRenderer, type RequestHandlerExtra } from '@mcp-ui/client';
|
||||
import type {
|
||||
McpUiDisplayMode,
|
||||
McpUiHostContext,
|
||||
@@ -23,7 +23,7 @@ import type {
|
||||
McpUiResourcePermissions,
|
||||
McpUiSizeChangedNotification,
|
||||
} from '@modelcontextprotocol/ext-apps/app-bridge';
|
||||
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
||||
import type { CallToolResult, JSONRPCRequest } from '@modelcontextprotocol/sdk/types.js';
|
||||
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
|
||||
import { callTool, readResource } from '../../api';
|
||||
import { AppEvents } from '../../constants/events';
|
||||
@@ -40,6 +40,8 @@ import {
|
||||
McpAppToolInputPartial,
|
||||
McpAppToolResult,
|
||||
DimensionLayout,
|
||||
SamplingCreateMessageParams,
|
||||
SamplingCreateMessageResponse,
|
||||
} from './types';
|
||||
|
||||
const DEFAULT_IFRAME_HEIGHT = 200;
|
||||
@@ -265,7 +267,13 @@ export default function McpAppRenderer({
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [containerWidth, setContainerWidth] = useState<number>(0);
|
||||
const [containerHeight, setContainerHeight] = useState<number>(0);
|
||||
const [apiHost, setApiHost] = useState<string | null>(null);
|
||||
const [secretKey, setSecretKey] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
window.electron.getGoosedHostPort().then(setApiHost);
|
||||
window.electron.getSecretKey().then(setSecretKey);
|
||||
}, []);
|
||||
|
||||
// Fetch the resource from the extension to get HTML and metadata (CSP, permissions, etc.).
|
||||
// If cachedHtml is provided we show it immediately; the fetch updates metadata and
|
||||
@@ -527,6 +535,42 @@ export default function McpAppRenderer({
|
||||
return () => observer.disconnect();
|
||||
}, []);
|
||||
|
||||
const handleFallbackRequest = useCallback(
|
||||
async (request: JSONRPCRequest, _extra: RequestHandlerExtra) => {
|
||||
if (request.method === 'sampling/createMessage') {
|
||||
if (!sessionId || !apiHost || !secretKey) {
|
||||
throw new Error('Session not initialized for sampling request');
|
||||
}
|
||||
const { messages, systemPrompt, maxTokens } =
|
||||
request.params as unknown as SamplingCreateMessageParams;
|
||||
const response = await fetch(`${apiHost}/sessions/${sessionId}/sampling/message`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Secret-Key': secretKey,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
messages: messages.map((m) => ({
|
||||
role: m.role,
|
||||
content: m.content,
|
||||
})),
|
||||
systemPrompt,
|
||||
maxTokens,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Sampling request failed: ${response.statusText}`);
|
||||
}
|
||||
return (await response.json()) as SamplingCreateMessageResponse;
|
||||
}
|
||||
return {
|
||||
status: 'error' as const,
|
||||
message: `Unhandled JSON-RPC method: ${request.method ?? '<unknown>'}`,
|
||||
};
|
||||
},
|
||||
[sessionId, apiHost, secretKey]
|
||||
);
|
||||
|
||||
const handleError = useCallback((err: Error) => {
|
||||
console.error('[MCP App Error]:', err);
|
||||
dispatch({ type: 'ERROR', message: errorMessage(err) });
|
||||
@@ -643,6 +687,7 @@ export default function McpAppRenderer({
|
||||
onReadResource={handleReadResource}
|
||||
onLoggingMessage={handleLoggingMessage}
|
||||
onSizeChanged={handleSizeChanged}
|
||||
onFallbackRequest={handleFallbackRequest}
|
||||
onError={handleError}
|
||||
/>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user