Skip to content

Commit 81fb9d1

Browse files
committed
update mcp_ui clear errors
1 parent 738b631 commit 81fb9d1

File tree

6 files changed

+126
-117
lines changed

6 files changed

+126
-117
lines changed

packages/app/eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import reactRefresh from 'eslint-plugin-react-refresh';
55
import tseslint from 'typescript-eslint';
66

77
export default tseslint.config(
8-
{ ignores: ['dist/**', 'node_modules/**'] },
8+
{ ignores: ['dist/**', 'node_modules/**', 'src/server/resources/gradio-widget-content.ts'] },
99

1010
// Server-side TypeScript files - use strict config
1111
{

packages/app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
"dependencies": {
5959
"@llmindset/hf-mcp": "workspace:^",
6060
"@huggingface/hub": "^2.1.0",
61-
"@mcp-ui/server": "^5.10.0",
61+
"@mcp-ui/server": "^5.12.0",
6262
"@modelcontextprotocol/sdk": "^1.20.0",
6363
"@radix-ui/react-checkbox": "^1.2.3",
6464
"@radix-ui/react-dropdown-menu": "^2.1.15",

packages/app/src/web/components/GradioWidgetDevShim.tsx

Lines changed: 103 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ const DEFAULT_WIDGET_STATE = {
99
lastPlayed: null,
1010
};
1111

12+
type MockOpenAiApi = Partial<OpenAiGlobals> & {
13+
setWidgetState: (state: unknown) => Promise<void>;
14+
callTool: (name: string, args: Record<string, unknown>) => Promise<{ result: string }>;
15+
sendFollowUpMessage: (args: { prompt: string }) => Promise<void>;
16+
openExternal: (payload: { href: string }) => void;
17+
requestDisplayMode: (args: { mode: DisplayMode }) => Promise<{ mode: DisplayMode }>;
18+
};
19+
20+
type OpenAiHostWindow = Window & { openai?: MockOpenAiApi };
21+
1222
export function GradioWidgetDevShim() {
1323
const iframeRef = useRef<HTMLIFrameElement>(null);
1424
const [toolOutputJson, setToolOutputJson] = useState(
@@ -56,13 +66,14 @@ export function GradioWidgetDevShim() {
5666
const iframe = iframeRef.current;
5767
if (!iframe?.contentWindow) return;
5868

59-
const iframeWindow = iframe.contentWindow as any;
60-
if (!iframeWindow.openai) return;
69+
const iframeWindow = iframe.contentWindow as OpenAiHostWindow;
70+
const openaiApi = iframeWindow.openai;
71+
if (!openaiApi) return;
6172

6273
// Update the globals
63-
iframeWindow.openai.displayMode = displayMode;
64-
iframeWindow.openai.maxHeight = maxHeight;
65-
iframeWindow.openai.theme = theme;
74+
openaiApi.displayMode = displayMode;
75+
openaiApi.maxHeight = maxHeight;
76+
openaiApi.theme = theme;
6677

6778
// Dispatch event to trigger hooks
6879
const event = new CustomEvent('openai:set_globals', {
@@ -74,40 +85,35 @@ export function GradioWidgetDevShim() {
7485
},
7586
},
7687
});
77-
iframeWindow.dispatchEvent(event);
88+
iframeWindow.dispatchEvent(event);
7889

79-
console.log('[Shim] Auto-updated displayMode, maxHeight, theme');
80-
}, [displayMode, maxHeight, theme]);
90+
console.log('[Shim] Auto-updated displayMode, maxHeight, theme');
91+
}, [displayMode, maxHeight, theme]);
8192

8293
// Initialize window.openai in iframe when it loads
83-
useEffect(() => {
84-
const iframe = iframeRef.current;
85-
if (!iframe) return;
86-
87-
const handleLoad = () => {
88-
const iframeWindow = iframe.contentWindow;
89-
if (!iframeWindow) return;
90-
91-
// Helper to dispatch custom events in iframe
92-
const dispatchGlobalsEvent = (globals: Partial<OpenAiGlobals>) => {
93-
const event = new CustomEvent('openai:set_globals', {
94-
detail: { globals },
95-
});
96-
iframeWindow.dispatchEvent(event);
97-
};
94+
useEffect(() => {
95+
const iframe = iframeRef.current;
96+
if (!iframe) return;
97+
98+
const handleLoad = () => {
99+
const iframeWindow = iframe.contentWindow;
100+
if (!iframeWindow) return;
101+
102+
// Helper to dispatch custom events in iframe
103+
const dispatchGlobalsEvent = (globals: Partial<OpenAiGlobals>) => {
104+
const event = new CustomEvent('openai:set_globals', {
105+
detail: { globals },
106+
});
107+
iframeWindow.dispatchEvent(event);
108+
};
98109

99-
// Mock the window.openai API
100-
const mockOpenAi: Partial<OpenAiGlobals> & {
101-
callTool: (name: string, args: Record<string, unknown>) => Promise<{ result: string }>;
102-
sendFollowUpMessage: (args: { prompt: string }) => Promise<void>;
103-
openExternal: (payload: { href: string }) => void;
104-
requestDisplayMode: (args: { mode: DisplayMode }) => Promise<{ mode: DisplayMode }>;
105-
} = {
106-
theme,
107-
locale: 'en-US',
108-
displayMode,
109-
maxHeight,
110-
toolInput: {},
110+
// Mock the window.openai API
111+
const mockOpenAi: MockOpenAiApi = {
112+
theme,
113+
locale: 'en-US',
114+
displayMode,
115+
maxHeight,
116+
toolInput: {},
111117
toolOutput: null,
112118
toolResponseMetadata: null,
113119
widgetState: null,
@@ -116,11 +122,11 @@ export function GradioWidgetDevShim() {
116122
capabilities: { hover: true, touch: false },
117123
},
118124
safeArea: {
119-
insets: { top: 0, bottom: 0, left: 0, right: 0 },
120-
},
121-
setWidgetState: async (state: unknown) => {
122-
console.log('[Shim] setWidgetState called:', state);
123-
setWidgetStateJson(JSON.stringify(state, null, 2));
125+
insets: { top: 0, bottom: 0, left: 0, right: 0 },
126+
},
127+
setWidgetState: async (state: unknown) => {
128+
console.log('[Shim] setWidgetState called:', state);
129+
setWidgetStateJson(JSON.stringify(state, null, 2));
124130
mockOpenAi.widgetState = state;
125131
dispatchGlobalsEvent({ widgetState: state });
126132
},
@@ -139,13 +145,14 @@ export function GradioWidgetDevShim() {
139145
console.log('[Shim] requestDisplayMode called:', args);
140146
setDisplayMode(args.mode);
141147
return { mode: args.mode };
142-
},
143-
};
148+
},
149+
};
144150

145-
// Inject into iframe's window BEFORE the widget loads
146-
(iframeWindow as any).openai = mockOpenAi;
151+
// Inject into iframe's window BEFORE the widget loads
152+
const hostWindow = iframeWindow as OpenAiHostWindow;
153+
hostWindow.openai = mockOpenAi;
147154

148-
console.log('[Shim] window.openai initialized in iframe');
155+
console.log('[Shim] window.openai initialized in iframe');
149156

150157
// Auto-send initial data after a short delay to ensure React is ready
151158
setTimeout(() => {
@@ -155,13 +162,13 @@ export function GradioWidgetDevShim() {
155162
? JSON.parse(widgetStateJson)
156163
: null;
157164

158-
mockOpenAi.toolOutput = toolOutput;
159-
mockOpenAi.widgetState = widgetState;
165+
mockOpenAi.toolOutput = toolOutput;
166+
mockOpenAi.widgetState = widgetState;
160167

161-
dispatchGlobalsEvent({
162-
toolOutput,
163-
widgetState,
164-
displayMode,
168+
dispatchGlobalsEvent({
169+
toolOutput,
170+
widgetState,
171+
displayMode,
165172
maxHeight,
166173
theme,
167174
});
@@ -171,38 +178,40 @@ export function GradioWidgetDevShim() {
171178
console.error('[Shim] Failed to send initial data:', e);
172179
}
173180
}, 100);
174-
};
181+
};
175182

176-
iframe.addEventListener('load', handleLoad);
177-
return () => iframe.removeEventListener('load', handleLoad);
178-
}, []); // Only run on mount
183+
iframe.addEventListener('load', handleLoad);
184+
return () => iframe.removeEventListener('load', handleLoad);
185+
// eslint-disable-next-line react-hooks/exhaustive-deps
186+
}, []);
179187

180188
const sendUpdate = () => {
181189
setError(null);
182-
const iframe = iframeRef.current;
183-
if (!iframe?.contentWindow) {
184-
setError('Iframe not loaded');
185-
return;
186-
}
190+
const iframe = iframeRef.current;
191+
if (!iframe?.contentWindow) {
192+
setError('Iframe not loaded');
193+
return;
194+
}
187195

188-
try {
196+
try {
189197
const toolOutput = JSON.parse(toolOutputJson);
190198
const widgetState = widgetStateJson.trim()
191199
? JSON.parse(widgetStateJson)
192200
: null;
193201

194-
const iframeWindow = iframe.contentWindow as any;
195-
if (!iframeWindow.openai) {
196-
setError('window.openai not initialized');
197-
return;
198-
}
202+
const iframeWindow = iframe.contentWindow as OpenAiHostWindow;
203+
const openaiApi = iframeWindow.openai;
204+
if (!openaiApi) {
205+
setError('window.openai not initialized');
206+
return;
207+
}
199208

200209
// Update the globals
201-
iframeWindow.openai.toolOutput = toolOutput;
202-
iframeWindow.openai.widgetState = widgetState;
203-
iframeWindow.openai.displayMode = displayMode;
204-
iframeWindow.openai.maxHeight = maxHeight;
205-
iframeWindow.openai.theme = theme;
210+
openaiApi.toolOutput = toolOutput;
211+
openaiApi.widgetState = widgetState;
212+
openaiApi.displayMode = displayMode;
213+
openaiApi.maxHeight = maxHeight;
214+
openaiApi.theme = theme;
206215

207216
// Dispatch event to trigger hooks
208217
const event = new CustomEvent('openai:set_globals', {
@@ -215,13 +224,13 @@ export function GradioWidgetDevShim() {
215224
theme,
216225
},
217226
},
218-
});
219-
iframeWindow.dispatchEvent(event);
227+
});
228+
iframeWindow.dispatchEvent(event);
220229

221-
console.log('[Shim] Update sent to widget');
222-
} catch (e) {
223-
setError(`Invalid JSON: ${e instanceof Error ? e.message : String(e)}`);
224-
}
230+
console.log('[Shim] Update sent to widget');
231+
} catch (e) {
232+
setError(`Invalid JSON: ${e instanceof Error ? e.message : String(e)}`);
233+
}
225234
};
226235

227236
return (
@@ -409,29 +418,30 @@ export function GradioWidgetDevShim() {
409418
<button
410419
onClick={() => {
411420
setToolOutputJson('');
412-
const iframe = iframeRef.current;
413-
if (iframe?.contentWindow) {
414-
const iframeWindow = iframe.contentWindow as any;
415-
if (iframeWindow.openai) {
416-
iframeWindow.openai.toolOutput = null;
417-
const event = new CustomEvent('openai:set_globals', {
418-
detail: {
419-
globals: {
420-
toolOutput: null,
421-
widgetState: widgetStateJson.trim()
421+
const iframe = iframeRef.current;
422+
if (iframe?.contentWindow) {
423+
const iframeWindow = iframe.contentWindow as OpenAiHostWindow;
424+
const openaiApi = iframeWindow.openai;
425+
if (openaiApi) {
426+
openaiApi.toolOutput = null;
427+
const event = new CustomEvent('openai:set_globals', {
428+
detail: {
429+
globals: {
430+
toolOutput: null,
431+
widgetState: widgetStateJson.trim()
422432
? JSON.parse(widgetStateJson)
423433
: null,
424434
displayMode,
425435
maxHeight,
426436
theme,
437+
},
427438
},
428-
},
429-
});
430-
iframeWindow.dispatchEvent(event);
431-
console.log('[Shim] Loading state activated');
439+
});
440+
iframeWindow.dispatchEvent(event);
441+
console.log('[Shim] Loading state activated');
442+
}
432443
}
433-
}
434-
}}
444+
}}
435445
className="w-full px-3 py-2 text-sm bg-purple-100 hover:bg-purple-200 rounded text-left"
436446
>
437447
Show Loading

packages/app/src/web/hooks/useWidgetState.ts

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,19 @@ export function useWidgetState<T extends UnknownObject>(
2727
_setWidgetState(widgetStateFromWindow);
2828
}, [widgetStateFromWindow]);
2929

30-
const setWidgetState = useCallback(
31-
(state: SetStateAction<T | null>) => {
32-
_setWidgetState((prevState) => {
33-
const newState = typeof state === 'function' ? state(prevState) : state;
34-
35-
if (newState != null) {
36-
window.openai.setWidgetState(newState);
37-
}
38-
39-
return newState;
40-
});
41-
},
42-
[window.openai.setWidgetState]
43-
);
30+
const setWidgetState = useCallback((state: SetStateAction<T | null>) => {
31+
const setWidgetStateFn = window.openai?.setWidgetState;
32+
33+
_setWidgetState((prevState) => {
34+
const newState = typeof state === 'function' ? state(prevState) : state;
35+
36+
if (newState != null && typeof setWidgetStateFn === 'function') {
37+
void setWidgetStateFn(newState);
38+
}
39+
40+
return newState;
41+
});
42+
}, []);
4443

4544
return [widgetState, setWidgetState] as const;
4645
}

packages/mcp/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
},
3434
"dependencies": {
3535
"@huggingface/hub": "^2.1.0",
36-
"@mcp-ui/server": "^5.10.0",
36+
"@mcp-ui/server": "^5.12.0",
3737
"turndown": "^7.2.0",
3838
"zod": "^3.24.4"
3939
},

pnpm-lock.yaml

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)