@gradio/core 0.23.2 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,602 @@
1
+ <script lang="ts">
2
+ import { Block } from "@gradio/atoms";
3
+ import CopyButton from "./CopyButton.svelte";
4
+
5
+ export let mcp_server_active: boolean;
6
+ export let mcp_server_url: string;
7
+ export let mcp_server_url_streamable: string;
8
+ export let tools: Tool[];
9
+ export let all_tools: Tool[] = [];
10
+ export let selected_tools: Set<string> = new Set();
11
+ export let mcp_json_sse: any;
12
+ export let mcp_json_stdio: any;
13
+ export let file_data_present: boolean;
14
+ export let mcp_docs: string;
15
+
16
+ interface ToolParameter {
17
+ title?: string;
18
+ type: string;
19
+ description: string;
20
+ format?: string;
21
+ default?: any;
22
+ }
23
+
24
+ interface Tool {
25
+ name: string;
26
+ description: string;
27
+ parameters: Record<string, ToolParameter>;
28
+ expanded?: boolean;
29
+ }
30
+
31
+ type Transport = "streamable_http" | "sse" | "stdio";
32
+ let current_transport: Transport = "streamable_http";
33
+ let include_file_upload = true;
34
+
35
+ const transports = [
36
+ ["streamable_http", "Streamable HTTP"],
37
+ ["sse", "SSE"],
38
+ ["stdio", "STDIO"]
39
+ ] as const;
40
+
41
+ $: display_url =
42
+ current_transport === "sse" ? mcp_server_url : mcp_server_url_streamable;
43
+
44
+ // Helper function to add/remove file upload tool from config
45
+ function update_config_with_file_upload(
46
+ base_config: any,
47
+ include_upload: boolean
48
+ ): any {
49
+ if (!base_config) return null;
50
+
51
+ const config = JSON.parse(JSON.stringify(base_config));
52
+
53
+ if (include_upload && file_data_present) {
54
+ const upload_file_mcp_server = {
55
+ command: "uvx",
56
+ args: [
57
+ "--from",
58
+ "gradio[mcp]",
59
+ "gradio",
60
+ "upload-mcp",
61
+ current_transport === "sse"
62
+ ? mcp_server_url
63
+ : mcp_server_url_streamable,
64
+ "<UPLOAD_DIRECTORY>"
65
+ ]
66
+ };
67
+ config.mcpServers.upload_files_to_gradio = upload_file_mcp_server;
68
+ } else {
69
+ delete config.mcpServers?.upload_files_to_gradio;
70
+ }
71
+
72
+ return config;
73
+ }
74
+
75
+ $: mcp_json_streamable_http = update_config_with_file_upload(
76
+ mcp_json_sse
77
+ ? {
78
+ ...mcp_json_sse,
79
+ mcpServers: {
80
+ ...mcp_json_sse.mcpServers,
81
+ gradio: {
82
+ ...mcp_json_sse.mcpServers.gradio,
83
+ url: mcp_server_url_streamable
84
+ }
85
+ }
86
+ }
87
+ : null,
88
+ include_file_upload
89
+ );
90
+
91
+ $: mcp_json_sse_updated = update_config_with_file_upload(
92
+ mcp_json_sse,
93
+ include_file_upload
94
+ );
95
+ $: mcp_json_stdio_updated = update_config_with_file_upload(
96
+ mcp_json_stdio,
97
+ include_file_upload
98
+ );
99
+ </script>
100
+
101
+ {#if mcp_server_active}
102
+ <div class="transport-selection">
103
+ <div class="snippets">
104
+ <span class="transport-label">Transport:</span>
105
+ {#each transports as [transport, display_name]}
106
+ <button
107
+ type="button"
108
+ class="snippet {current_transport === transport
109
+ ? 'current-lang'
110
+ : 'inactive-lang'}"
111
+ on:click={() => (current_transport = transport)}
112
+ >
113
+ {display_name}
114
+ </button>
115
+ {/each}
116
+ </div>
117
+ </div>
118
+
119
+ {#if current_transport !== "stdio"}
120
+ <Block>
121
+ <div class="mcp-url">
122
+ <label for="mcp-server-url"
123
+ ><span class="status-indicator active">●</span>MCP Server URL ({current_transport ===
124
+ "sse"
125
+ ? "SSE"
126
+ : "Streamable HTTP"})</label
127
+ >
128
+ <div class="textbox">
129
+ <input id="mcp-server-url" type="text" readonly value={display_url} />
130
+ <CopyButton code={display_url} />
131
+ </div>
132
+ </div>
133
+ </Block>
134
+ <p>&nbsp;</p>
135
+ {/if}
136
+
137
+ <div class="tool-selection">
138
+ <strong
139
+ >{all_tools.length > 0 ? all_tools.length : tools.length} Available MCP Tools</strong
140
+ >
141
+ {#if all_tools.length > 0}
142
+ <div class="tool-selection-controls">
143
+ <button
144
+ class="select-all-btn"
145
+ on:click={() => {
146
+ selected_tools = new Set(all_tools.map((t) => t.name));
147
+ }}
148
+ >
149
+ Select All
150
+ </button>
151
+ <button
152
+ class="select-none-btn"
153
+ on:click={() => {
154
+ selected_tools = new Set();
155
+ }}
156
+ >
157
+ Select None
158
+ </button>
159
+ </div>
160
+ {/if}
161
+ </div>
162
+ <div class="mcp-tools">
163
+ {#each all_tools.length > 0 ? all_tools : tools as tool}
164
+ <div class="tool-item">
165
+ <div class="tool-header-wrapper">
166
+ {#if all_tools.length > 0}
167
+ <input
168
+ type="checkbox"
169
+ class="tool-checkbox"
170
+ checked={selected_tools.has(tool.name) ||
171
+ current_transport !== "streamable_http"}
172
+ disabled={current_transport !== "streamable_http"}
173
+ style={current_transport !== "streamable_http"
174
+ ? "opacity: 0.5; cursor: not-allowed;"
175
+ : ""}
176
+ on:change={(e) => {
177
+ if (e.currentTarget.checked) {
178
+ selected_tools.add(tool.name);
179
+ } else {
180
+ selected_tools.delete(tool.name);
181
+ }
182
+ selected_tools = selected_tools;
183
+ }}
184
+ />
185
+ {/if}
186
+ <button
187
+ class="tool-header"
188
+ on:click={() => (tool.expanded = !tool.expanded)}
189
+ >
190
+ <span
191
+ ><span class="tool-name">{tool.name}</span> &nbsp;
192
+ <span class="tool-description"
193
+ >{tool.description
194
+ ? tool.description
195
+ : "⚠︎ No description provided in function docstring"}</span
196
+ ></span
197
+ >
198
+ <span class="tool-arrow">{tool.expanded ? "▼" : "▶"}</span>
199
+ </button>
200
+ </div>
201
+ {#if tool.expanded}
202
+ <div class="tool-content">
203
+ {#if Object.keys(tool.parameters).length > 0}
204
+ <div class="tool-parameters">
205
+ {#each Object.entries(tool.parameters) as [name, param]}
206
+ <div class="parameter">
207
+ <code>{name}</code>
208
+ <span class="parameter-type">
209
+ ({param.type}{param.default !== undefined
210
+ ? `, default: ${JSON.stringify(param.default)}`
211
+ : ""})
212
+ </span>
213
+ <p class="parameter-description">
214
+ {param.description
215
+ ? param.description
216
+ : "⚠︎ No description for this parameter in function docstring"}
217
+ </p>
218
+ </div>
219
+ {/each}
220
+ </div>
221
+ {:else}
222
+ <p>Takes no input parameters</p>
223
+ {/if}
224
+ </div>
225
+ {/if}
226
+ </div>
227
+ {/each}
228
+ </div>
229
+ <p>&nbsp;</p>
230
+
231
+ {#if current_transport === "streamable_http"}
232
+ <strong>Streamable HTTP Transport</strong>: To add this MCP to clients that
233
+ support Streamable HTTP, simply add the following configuration to your MCP
234
+ config.
235
+ <p>&nbsp;</p>
236
+ <Block>
237
+ <code>
238
+ <div class="copy">
239
+ <CopyButton
240
+ code={JSON.stringify(mcp_json_streamable_http, null, 2)}
241
+ />
242
+ </div>
243
+ <div>
244
+ <pre>{JSON.stringify(mcp_json_streamable_http, null, 2)}</pre>
245
+ </div>
246
+ </code>
247
+ </Block>
248
+ {:else if current_transport === "sse"}
249
+ <strong>SSE Transport</strong>: The SSE transport has been deprecated by the
250
+ MCP spec. We recommend using the Streamable HTTP transport instead. But to
251
+ add this MCP to clients that only support server-sent events (SSE), simply
252
+ add the following configuration to your MCP config.
253
+ <p>&nbsp;</p>
254
+ <Block>
255
+ <code>
256
+ <div class="copy">
257
+ <CopyButton code={JSON.stringify(mcp_json_sse_updated, null, 2)} />
258
+ </div>
259
+ <div>
260
+ <pre>{JSON.stringify(mcp_json_sse_updated, null, 2)}</pre>
261
+ </div>
262
+ </code>
263
+ </Block>
264
+ {:else if current_transport === "stdio"}
265
+ <strong>STDIO Transport</strong>: For clients that only support stdio (e.g.
266
+ Claude Desktop), first
267
+ <a href="https://nodejs.org/en/download/" target="_blank">install Node.js</a
268
+ >. Then, you can use the following command:
269
+ <p>&nbsp;</p>
270
+ <Block>
271
+ <code>
272
+ <div class="copy">
273
+ <CopyButton code={JSON.stringify(mcp_json_stdio_updated, null, 2)} />
274
+ </div>
275
+ <div>
276
+ <pre>{JSON.stringify(mcp_json_stdio_updated, null, 2)}</pre>
277
+ </div>
278
+ </code>
279
+ </Block>
280
+ {/if}
281
+ {#if file_data_present}
282
+ <div class="file-upload-section">
283
+ <label class="checkbox-label">
284
+ <input
285
+ type="checkbox"
286
+ bind:checked={include_file_upload}
287
+ class="checkbox"
288
+ />
289
+ Include Gradio file upload tool
290
+ </label>
291
+ <p class="file-upload-explanation">
292
+ The <code>upload_files_to_gradio</code> tool uploads files from your
293
+ local <code>UPLOAD_DIRECTORY</code> (or any of its subdirectories) to
294
+ the Gradio app. This is needed because MCP servers require files to be
295
+ provided as URLs. You can omit this tool if you prefer to upload files
296
+ manually. This tool requires
297
+ <a
298
+ href="https://docs.astral.sh/uv/getting-started/installation/"
299
+ target="_blank">uv</a
300
+ > to be installed.
301
+ </p>
302
+ </div>
303
+ {/if}
304
+
305
+ <p>&nbsp;</p>
306
+ <p>
307
+ <a href={mcp_docs} target="_blank">
308
+ Read more about MCP in the Gradio docs
309
+ </a>
310
+ </p>
311
+ {:else}
312
+ This Gradio app can also serve as an MCP server, with an MCP tool
313
+ corresponding to each API endpoint. To enable this, launch this Gradio app
314
+ with <code>.launch(mcp_server=True)</code> or set the
315
+ <code>GRADIO_MCP_SERVER</code>
316
+ env variable to
317
+ <code>"True"</code>.
318
+ {/if}
319
+
320
+ <style>
321
+ .transport-selection {
322
+ margin-bottom: var(--size-4);
323
+ }
324
+
325
+ .snippets {
326
+ display: flex;
327
+ align-items: center;
328
+ margin-bottom: var(--size-4);
329
+ }
330
+
331
+ .snippets > * + * {
332
+ margin-left: var(--size-2);
333
+ }
334
+
335
+ .transport-label {
336
+ font-weight: 600;
337
+ color: var(--body-text-color);
338
+ margin-right: var(--size-2);
339
+ }
340
+
341
+ .file-upload-section {
342
+ margin-top: var(--size-4);
343
+ padding: var(--size-3);
344
+ border: 1px solid var(--border-color-primary);
345
+ border-radius: var(--radius-md);
346
+ background: var(--background-fill-secondary);
347
+ }
348
+
349
+ .checkbox-label {
350
+ display: flex;
351
+ align-items: center;
352
+ font-weight: 600;
353
+ color: var(--body-text-color);
354
+ cursor: pointer;
355
+ margin-bottom: var(--size-2);
356
+ }
357
+
358
+ .checkbox {
359
+ margin-right: var(--size-2);
360
+ width: var(--size-4);
361
+ height: var(--size-4);
362
+ cursor: pointer;
363
+ accent-color: var(--color-accent);
364
+ }
365
+
366
+ .checkbox:checked {
367
+ background-color: var(--color-accent);
368
+ border-color: var(--color-accent);
369
+ }
370
+
371
+ .file-upload-explanation {
372
+ margin: 0;
373
+ color: var(--body-text-color);
374
+ }
375
+
376
+ .snippet {
377
+ display: flex;
378
+ align-items: center;
379
+ border: 1px solid var(--border-color-primary);
380
+ border-radius: var(--radius-md);
381
+ padding: var(--size-1) var(--size-1-5);
382
+ color: var(--body-text-color-subdued);
383
+ color: var(--body-text-color);
384
+ line-height: 1;
385
+ user-select: none;
386
+ }
387
+
388
+ .current-lang {
389
+ border: 1px solid var(--body-text-color-subdued);
390
+ color: var(--body-text-color);
391
+ }
392
+
393
+ .inactive-lang {
394
+ cursor: pointer;
395
+ color: var(--body-text-color-subdued);
396
+ }
397
+
398
+ .inactive-lang:hover,
399
+ .inactive-lang:focus {
400
+ box-shadow: var(--shadow-drop);
401
+ color: var(--body-text-color);
402
+ }
403
+
404
+ code pre {
405
+ overflow-x: auto;
406
+ color: var(--body-text-color);
407
+ font-family: var(--font-mono);
408
+ tab-size: 2;
409
+ }
410
+
411
+ .copy {
412
+ position: absolute;
413
+ top: 0;
414
+ right: 0;
415
+ margin-top: 5px;
416
+ margin-right: 5px;
417
+ z-index: 10;
418
+ }
419
+
420
+ .mcp-url {
421
+ padding: var(--size-2);
422
+ position: relative;
423
+ }
424
+
425
+ .mcp-url label {
426
+ display: block;
427
+ margin-bottom: var(--size-2);
428
+ font-weight: 600;
429
+ color: var(--body-text-color);
430
+ }
431
+
432
+ .mcp-url .textbox {
433
+ display: flex;
434
+ align-items: center;
435
+ gap: var(--size-2);
436
+ border: 1px solid var(--border-color-primary);
437
+ border-radius: var(--radius-sm);
438
+ padding: var(--size-2);
439
+ background: var(--background-fill-primary);
440
+ }
441
+
442
+ .mcp-url input {
443
+ flex: 1;
444
+ border: none;
445
+ background: none;
446
+ color: var(--body-text-color);
447
+ font-family: var(--font-mono);
448
+ font-size: var(--text-md);
449
+ width: 100%;
450
+ }
451
+
452
+ .mcp-url input:focus {
453
+ outline: none;
454
+ }
455
+
456
+ .status-indicator {
457
+ display: inline-block;
458
+ margin-right: var(--size-1-5);
459
+ position: relative;
460
+ top: -1px;
461
+ font-size: 0.8em;
462
+ }
463
+
464
+ .status-indicator.active {
465
+ color: #4caf50;
466
+ animation: pulse 1s infinite;
467
+ }
468
+
469
+ @keyframes pulse {
470
+ 0% {
471
+ opacity: 1;
472
+ }
473
+ 50% {
474
+ opacity: 0.6;
475
+ }
476
+ 100% {
477
+ opacity: 1;
478
+ }
479
+ }
480
+
481
+ .mcp-tools {
482
+ margin-top: var(--size-4);
483
+ border: 1px solid var(--border-color-primary);
484
+ border-radius: var(--radius-md);
485
+ overflow: hidden;
486
+ }
487
+
488
+ .tool-selection {
489
+ display: flex;
490
+ justify-content: space-between;
491
+ align-items: center;
492
+ margin-bottom: var(--size-2);
493
+ }
494
+
495
+ .tool-selection-controls {
496
+ display: flex;
497
+ gap: var(--size-2);
498
+ }
499
+
500
+ .select-all-btn,
501
+ .select-none-btn {
502
+ padding: var(--size-1) var(--size-2);
503
+ border: 1px solid var(--border-color-primary);
504
+ border-radius: var(--radius-sm);
505
+ background: var(--background-fill-primary);
506
+ color: var(--body-text-color);
507
+ cursor: pointer;
508
+ font-size: var(--text-sm);
509
+ }
510
+
511
+ .select-all-btn:hover,
512
+ .select-none-btn:hover {
513
+ background: var(--background-fill-secondary);
514
+ }
515
+
516
+ .tool-item {
517
+ border-bottom: 1px solid var(--border-color-primary);
518
+ }
519
+
520
+ .tool-item:last-child {
521
+ border-bottom: none;
522
+ }
523
+
524
+ .tool-header-wrapper {
525
+ display: flex;
526
+ align-items: center;
527
+ }
528
+
529
+ .tool-checkbox {
530
+ margin-left: var(--size-3);
531
+ margin-right: var(--size-2);
532
+ width: var(--size-4);
533
+ height: var(--size-4);
534
+ cursor: pointer;
535
+ accent-color: var(--color-accent);
536
+ }
537
+
538
+ .tool-checkbox:checked {
539
+ background-color: var(--color-accent);
540
+ border-color: var(--color-accent);
541
+ }
542
+
543
+ .tool-header {
544
+ flex: 1;
545
+ display: flex;
546
+ justify-content: space-between;
547
+ align-items: center;
548
+ padding: var(--size-3);
549
+ background: var(--background-fill-primary);
550
+ border: none;
551
+ cursor: pointer;
552
+ text-align: left;
553
+ }
554
+
555
+ .tool-header:hover {
556
+ background: var(--background-fill-secondary);
557
+ }
558
+
559
+ .tool-name {
560
+ font-family: var(--font-mono);
561
+ font-weight: 600;
562
+ }
563
+
564
+ .tool-arrow {
565
+ color: var(--body-text-color-subdued);
566
+ }
567
+
568
+ .tool-content {
569
+ padding: var(--size-3);
570
+ background: var(--background-fill-secondary);
571
+ }
572
+
573
+ .tool-description {
574
+ margin-bottom: var(--size-3);
575
+ color: var(--body-text-color);
576
+ }
577
+ .parameter {
578
+ margin-bottom: var(--size-2);
579
+ padding: var(--size-2);
580
+ background: var(--background-fill-primary);
581
+ border-radius: var(--radius-sm);
582
+ }
583
+
584
+ .parameter code {
585
+ font-weight: 600;
586
+ color: var(--color-accent);
587
+ }
588
+
589
+ .parameter-type {
590
+ color: var(--body-text-color-subdued);
591
+ margin-left: var(--size-1);
592
+ }
593
+
594
+ .parameter-description {
595
+ margin-top: var(--size-1);
596
+ color: var(--body-text-color);
597
+ }
598
+
599
+ a {
600
+ text-decoration: underline;
601
+ }
602
+ </style>