@gradio/core 0.15.0 → 0.16.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.
@@ -12,11 +12,14 @@
12
12
  import InstallSnippet from "./InstallSnippet.svelte";
13
13
  import CodeSnippet from "./CodeSnippet.svelte";
14
14
  import RecordingSnippet from "./RecordingSnippet.svelte";
15
+ import CopyButton from "./CopyButton.svelte";
16
+ import { Block } from "@gradio/atoms";
15
17
 
16
18
  import python from "./img/python.svg";
17
19
  import javascript from "./img/javascript.svg";
18
20
  import bash from "./img/bash.svg";
19
21
  import ResponseSnippet from "./ResponseSnippet.svelte";
22
+ import mcp from "./img/mcp.svg";
20
23
 
21
24
  export let dependencies: Dependency[];
22
25
  export let root: string;
@@ -45,15 +48,17 @@
45
48
  }
46
49
 
47
50
  export let api_calls: Payload[] = [];
48
- let current_language: "python" | "javascript" | "bash" = "python";
51
+ let current_language: "python" | "javascript" | "bash" | "mcp" = "python";
49
52
 
50
53
  const langs = [
51
- ["python", python],
52
- ["javascript", javascript],
53
- ["bash", bash]
54
+ ["python", "Python", python],
55
+ ["javascript", "JavaScript", javascript],
56
+ ["bash", "cURL", bash],
57
+ ["mcp", "MCP", mcp]
54
58
  ] as const;
55
59
 
56
60
  let is_running = false;
61
+ let mcp_server_active = false;
57
62
 
58
63
  async function get_info(): Promise<{
59
64
  named_endpoints: any;
@@ -87,11 +92,59 @@
87
92
 
88
93
  const dispatch = createEventDispatcher();
89
94
 
95
+ const mcp_server_url = `${root}gradio_api/mcp/sse`;
96
+
97
+ interface ToolParameter {
98
+ title?: string;
99
+ type: string;
100
+ description: string;
101
+ format?: string;
102
+ }
103
+
104
+ interface Tool {
105
+ name: string;
106
+ description: string;
107
+ parameters: Record<string, ToolParameter>;
108
+ expanded?: boolean;
109
+ }
110
+
111
+ let tools: Tool[] = [];
112
+
113
+ async function fetchMcpTools() {
114
+ try {
115
+ const response = await fetch(`${root}gradio_api/mcp/schema`);
116
+ const schema = await response.json();
117
+
118
+ tools = Object.entries(schema).map(([name, tool]: [string, any]) => ({
119
+ name: `${name}`,
120
+ description: tool.description || "",
121
+ parameters: tool.properties || {},
122
+ expanded: false
123
+ }));
124
+ } catch (error) {
125
+ console.error("Failed to fetch MCP tools:", error);
126
+ tools = [];
127
+ }
128
+ }
129
+
90
130
  onMount(() => {
91
131
  document.body.style.overflow = "hidden";
92
132
  if ("parentIFrame" in window) {
93
133
  window.parentIFrame?.scrollTo(0, 0);
94
134
  }
135
+
136
+ // Check MCP server status and fetch tools if active
137
+ fetch(mcp_server_url)
138
+ .then((response) => {
139
+ mcp_server_active = response.ok;
140
+ if (mcp_server_active) {
141
+ fetchMcpTools();
142
+ }
143
+ })
144
+ .catch(() => {
145
+ mcp_server_active = false;
146
+ });
147
+
95
148
  return () => {
96
149
  document.body.style.overflow = "auto";
97
150
  };
@@ -101,26 +154,30 @@
101
154
  {#if info}
102
155
  {#if api_count}
103
156
  <div class="banner-wrap">
104
- <ApiBanner on:close root={space_id || root} {api_count} />
157
+ <ApiBanner
158
+ on:close
159
+ root={space_id || root}
160
+ {api_count}
161
+ {current_language}
162
+ />
105
163
  </div>
106
164
 
107
165
  <div class="docs-wrap">
108
166
  <div class="client-doc">
109
167
  <p style="font-size: var(--text-lg);">
110
- Choose a language to see the code snippets for interacting with the
111
- API.
168
+ Choose one of the following ways to interact with the API.
112
169
  </p>
113
170
  </div>
114
171
  <div class="endpoint">
115
172
  <div class="snippets">
116
- {#each langs as [language, img]}
173
+ {#each langs as [language, display_name, img]}
117
174
  <li
118
175
  class="snippet
119
176
  {current_language === language ? 'current-lang' : 'inactive-lang'}"
120
177
  on:click={() => (current_language = language)}
121
178
  >
122
179
  <img src={img} alt="" />
123
- {language}
180
+ {display_name}
124
181
  </li>
125
182
  {/each}
126
183
  </div>
@@ -169,100 +226,256 @@
169
226
  href={current_language == "python" ? py_docs : js_docs}
170
227
  target="_blank">docs</a
171
228
  >) if you don't already have it installed.
229
+ {:else if current_language == "mcp"}
230
+ {#if mcp_server_active}
231
+ <Block>
232
+ <div class="mcp-url">
233
+ <label
234
+ ><span class="status-indicator active">●</span>MCP Server
235
+ URL</label
236
+ >
237
+ <div class="textbox">
238
+ <input type="text" readonly value={mcp_server_url} />
239
+ <CopyButton code={mcp_server_url} />
240
+ </div>
241
+ </div>
242
+ </Block>
243
+ <p>&nbsp;</p>
244
+ <strong>Available MCP Tools</strong>
245
+ <div class="mcp-tools">
246
+ {#each tools as tool}
247
+ <div class="tool-item">
248
+ <button
249
+ class="tool-header"
250
+ on:click={() => (tool.expanded = !tool.expanded)}
251
+ >
252
+ <span
253
+ ><span class="tool-name">{tool.name}</span> &nbsp;
254
+ <span class="tool-description"
255
+ >{tool.description
256
+ ? tool.description
257
+ : "⚠︎ No description provided in function docstring"}</span
258
+ ></span
259
+ >
260
+ <span class="tool-arrow"
261
+ >{tool.expanded ? "▼" : "▶"}</span
262
+ >
263
+ </button>
264
+ {#if tool.expanded}
265
+ <div class="tool-content">
266
+ {#if Object.keys(tool.parameters).length > 0}
267
+ <div class="tool-parameters">
268
+ {#if Object.keys(tool.parameters).length > 0}
269
+ {#each Object.entries(tool.parameters) as [name, param]}
270
+ <div class="parameter">
271
+ <code>{name}</code>
272
+ <span class="parameter-type"
273
+ >({param.type})</span
274
+ >
275
+ <p class="parameter-description">
276
+ {param.description
277
+ ? param.description
278
+ : "⚠︎ No description for this parameter in function docstring"}
279
+ </p>
280
+ </div>
281
+ {/each}
282
+ {:else}
283
+ <p>No parameters</p>
284
+ {/if}
285
+ </div>
286
+ {/if}
287
+ </div>
288
+ {/if}
289
+ </div>
290
+ {/each}
291
+ </div>
292
+ <p>&nbsp;</p>
293
+
294
+ <strong>Integration</strong>: To add this MCP to clients that
295
+ support SSE (e.g. Cursor, Windsurf, Cline), simply add the
296
+ following configuration to your MCP config:
297
+ <p>&nbsp;</p>
298
+ <Block>
299
+ <code>
300
+ <div class="copy">
301
+ <CopyButton
302
+ code={JSON.stringify(
303
+ {
304
+ mcpServers: {
305
+ gradio: {
306
+ url: mcp_server_url
307
+ }
308
+ }
309
+ },
310
+ null,
311
+ 2
312
+ )}
313
+ />
314
+ </div>
315
+ <div>
316
+ <pre>{JSON.stringify(
317
+ {
318
+ mcpServers: {
319
+ gradio: {
320
+ url: mcp_server_url
321
+ }
322
+ }
323
+ },
324
+ null,
325
+ 2
326
+ )}</pre>
327
+ </div>
328
+ </code>
329
+ </Block>
330
+ <p>&nbsp;</p>
331
+ <em>Experimental stdio support</em>: For clients that only
332
+ support stdio, first
333
+ <a href="https://nodejs.org/en/download/" target="_blank"
334
+ >install Node.js</a
335
+ >. Then, you can use the following command:
336
+ <p>&nbsp;</p>
337
+ <Block>
338
+ <code>
339
+ <div class="copy">
340
+ <CopyButton
341
+ code={JSON.stringify(
342
+ {
343
+ mcpServers: {
344
+ gradio: {
345
+ command: "npx",
346
+ args: ["mcp-remote", mcp_server_url]
347
+ }
348
+ }
349
+ },
350
+ null,
351
+ 2
352
+ )}
353
+ />
354
+ </div>
355
+ <div>
356
+ <pre>{JSON.stringify(
357
+ {
358
+ mcpServers: {
359
+ gradio: {
360
+ command: "npx",
361
+ arguments: ["mcp-remote", mcp_server_url]
362
+ }
363
+ }
364
+ },
365
+ null,
366
+ 2
367
+ )}</pre>
368
+ </div>
369
+ </code>
370
+ </Block>
371
+ <p>&nbsp;</p>
372
+ <p>&nbsp;</p>
373
+ {:else}
374
+ This Gradio app can also serve as an MCP server, with an MCP
375
+ tool corresponding to each API endpoint. To enable this, launch
376
+ this Gradio app with <code>.launch(mcp_server=True)</code> or
377
+ set the <code>GRADIO_MCP_SERVER</code> env variable to
378
+ <code>"True"</code>.
379
+ {/if}
172
380
  {:else}
173
381
  1. Confirm that you have cURL installed on your system.
174
382
  {/if}
175
383
  </p>
176
384
 
177
- <InstallSnippet {current_language} />
178
-
179
- <p class="padded">
180
- 2. Find the API endpoint below corresponding to your desired
181
- function in the app. Copy the code snippet, replacing the
182
- placeholder values with your own input data.
183
- {#if space_id}If this is a private Space, you may need to pass your
184
- Hugging Face token as well (<a
185
- href={current_language == "python"
186
- ? py_docs + spaces_docs_suffix
187
- : current_language == "javascript"
188
- ? js_docs + spaces_docs_suffix
189
- : bash_docs}
190
- class="underline"
191
- target="_blank">read more</a
192
- >).{/if}
193
-
194
- Or use the
195
- <Button
196
- size="sm"
197
- variant="secondary"
198
- on:click={() => dispatch("close", { api_recorder_visible: true })}
199
- >
200
- <div class="loading-dot"></div>
201
- <p class="self-baseline">API Recorder</p>
202
- </Button>
203
- to automatically generate your API requests.
204
- {#if current_language == "bash"}<br />&nbsp;<br />Making a
205
- prediction and getting a result requires
206
- <strong>2 requests</strong>: a
207
- <code>POST</code>
208
- and a <code>GET</code> request. The <code>POST</code> request
209
- returns an <code>EVENT_ID</code>, which is used in the second
210
- <code>GET</code> request to fetch the results. In these snippets,
211
- we've used <code>awk</code> and <code>read</code> to parse the
212
- results, combining these two requests into one command for ease of
213
- use. {#if username !== null}
214
- Note: connecting to an authenticated app requires an additional
215
- request.{/if} See
216
- <a href={bash_docs} target="_blank">curl docs</a>.
217
- {/if}
218
-
219
- <!-- <span
385
+ {#if current_language !== "mcp"}
386
+ <InstallSnippet {current_language} />
387
+
388
+ <p class="padded">
389
+ 2. Find the API endpoint below corresponding to your desired
390
+ function in the app. Copy the code snippet, replacing the
391
+ placeholder values with your own input data.
392
+ {#if space_id}If this is a private Space, you may need to pass
393
+ your Hugging Face token as well (<a
394
+ href={current_language == "python"
395
+ ? py_docs + spaces_docs_suffix
396
+ : current_language == "javascript"
397
+ ? js_docs + spaces_docs_suffix
398
+ : bash_docs}
399
+ class="underline"
400
+ target="_blank">read more</a
401
+ >).{/if}
402
+
403
+ Or use the
404
+ <Button
405
+ size="sm"
406
+ variant="secondary"
407
+ on:click={() =>
408
+ dispatch("close", { api_recorder_visible: true })}
409
+ >
410
+ <div class="loading-dot"></div>
411
+ <p class="self-baseline">API Recorder</p>
412
+ </Button>
413
+ to automatically generate your API requests.
414
+ {#if current_language == "bash"}<br />&nbsp;<br />Making a
415
+ prediction and getting a result requires
416
+ <strong>2 requests</strong>: a
417
+ <code>POST</code>
418
+ and a <code>GET</code> request. The <code>POST</code> request
419
+ returns an <code>EVENT_ID</code>, which is used in the second
420
+ <code>GET</code> request to fetch the results. In these
421
+ snippets, we've used <code>awk</code> and <code>read</code> to
422
+ parse the results, combining these two requests into one command
423
+ for ease of use. {#if username !== null}
424
+ Note: connecting to an authenticated app requires an
425
+ additional request.{/if} See
426
+ <a href={bash_docs} target="_blank">curl docs</a>.
427
+ {/if}
428
+
429
+ <!-- <span
220
430
  id="api-recorder"
221
431
  on:click={() => dispatch("close", { api_recorder_visible: true })}
222
432
  >🪄 API Recorder</span
223
433
  > to automatically generate your API requests! -->
224
- </p>
434
+ </p>
435
+ {/if}
225
436
  {/if}
226
437
 
227
- {#each dependencies as dependency, dependency_index}
228
- {#if dependency.show_api && info.named_endpoints["/" + dependency.api_name]}
229
- <div class="endpoint-container">
230
- <CodeSnippet
231
- named={true}
232
- endpoint_parameters={info.named_endpoints[
233
- "/" + dependency.api_name
234
- ].parameters}
235
- {dependency}
236
- {dependency_index}
237
- {current_language}
238
- {root}
239
- {space_id}
240
- {username}
241
- api_prefix={app.api_prefix}
242
- />
243
-
244
- <ParametersSnippet
245
- endpoint_returns={info.named_endpoints[
246
- "/" + dependency.api_name
247
- ].parameters}
248
- js_returns={js_info.named_endpoints["/" + dependency.api_name]
249
- .parameters}
250
- {is_running}
251
- {current_language}
252
- />
253
-
254
- <ResponseSnippet
255
- endpoint_returns={info.named_endpoints[
256
- "/" + dependency.api_name
257
- ].returns}
258
- js_returns={js_info.named_endpoints["/" + dependency.api_name]
259
- .returns}
260
- {is_running}
261
- {current_language}
262
- />
263
- </div>
264
- {/if}
265
- {/each}
438
+ {#if current_language !== "mcp"}
439
+ {#each dependencies as dependency, dependency_index}
440
+ {#if dependency.show_api && info.named_endpoints["/" + dependency.api_name]}
441
+ <div class="endpoint-container">
442
+ <CodeSnippet
443
+ named={true}
444
+ endpoint_parameters={info.named_endpoints[
445
+ "/" + dependency.api_name
446
+ ].parameters}
447
+ {dependency}
448
+ {dependency_index}
449
+ {current_language}
450
+ {root}
451
+ {space_id}
452
+ {username}
453
+ api_prefix={app.api_prefix}
454
+ />
455
+
456
+ <ParametersSnippet
457
+ endpoint_returns={info.named_endpoints[
458
+ "/" + dependency.api_name
459
+ ].parameters}
460
+ js_returns={js_info.named_endpoints["/" + dependency.api_name]
461
+ .parameters}
462
+ {is_running}
463
+ {current_language}
464
+ />
465
+
466
+ <ResponseSnippet
467
+ endpoint_returns={info.named_endpoints[
468
+ "/" + dependency.api_name
469
+ ].returns}
470
+ js_returns={js_info.named_endpoints["/" + dependency.api_name]
471
+ .returns}
472
+ {is_running}
473
+ {current_language}
474
+ />
475
+ </div>
476
+ {/if}
477
+ {/each}
478
+ {/if}
266
479
  </div>
267
480
  </div>
268
481
  {:else}
@@ -336,7 +549,6 @@
336
549
  color: var(--body-text-color);
337
550
  line-height: 1;
338
551
  user-select: none;
339
- text-transform: capitalize;
340
552
  }
341
553
 
342
554
  .current-lang {
@@ -419,4 +631,173 @@
419
631
  font-family: var(--font-mono);
420
632
  font-size: var(--text-md);
421
633
  }
634
+
635
+ code pre {
636
+ overflow-x: auto;
637
+ color: var(--body-text-color);
638
+ font-family: var(--font-mono);
639
+ tab-size: 2;
640
+ }
641
+
642
+ .token.string {
643
+ display: contents;
644
+ color: var(--color-accent-base);
645
+ }
646
+
647
+ .copy {
648
+ position: absolute;
649
+ top: 0;
650
+ right: 0;
651
+ margin-top: 5px;
652
+ margin-right: 5px;
653
+ z-index: 10;
654
+ }
655
+
656
+ .container {
657
+ display: flex;
658
+ flex-direction: column;
659
+ gap: var(--spacing-xxl);
660
+ margin-top: var(--size-3);
661
+ margin-bottom: var(--size-3);
662
+ }
663
+
664
+ .desc {
665
+ color: var(--body-text-color-subdued);
666
+ }
667
+
668
+ .api-name {
669
+ color: var(--color-accent);
670
+ }
671
+
672
+ .mcp-url {
673
+ padding: var(--size-2);
674
+ position: relative;
675
+ }
676
+
677
+ .mcp-url label {
678
+ display: block;
679
+ margin-bottom: var(--size-2);
680
+ font-weight: 600;
681
+ color: var(--body-text-color);
682
+ }
683
+
684
+ .mcp-url .textbox {
685
+ display: flex;
686
+ align-items: center;
687
+ gap: var(--size-2);
688
+ border: 1px solid var(--border-color-primary);
689
+ border-radius: var(--radius-sm);
690
+ padding: var(--size-2);
691
+ background: var(--background-fill-primary);
692
+ }
693
+
694
+ .mcp-url input {
695
+ flex: 1;
696
+ border: none;
697
+ background: none;
698
+ color: var(--body-text-color);
699
+ font-family: var(--font-mono);
700
+ font-size: var(--text-md);
701
+ width: 100%;
702
+ }
703
+
704
+ .mcp-url input:focus {
705
+ outline: none;
706
+ }
707
+
708
+ .status-indicator {
709
+ display: inline-block;
710
+ margin-right: var(--size-1-5);
711
+ position: relative;
712
+ top: -1px;
713
+ font-size: 0.8em;
714
+ }
715
+
716
+ .status-indicator.active {
717
+ color: #4caf50;
718
+ animation: pulse 1s infinite;
719
+ }
720
+
721
+ @keyframes pulse {
722
+ 0% {
723
+ opacity: 1;
724
+ }
725
+ 50% {
726
+ opacity: 0.6;
727
+ }
728
+ 100% {
729
+ opacity: 1;
730
+ }
731
+ }
732
+
733
+ .mcp-tools {
734
+ margin-top: var(--size-4);
735
+ border: 1px solid var(--border-color-primary);
736
+ border-radius: var(--radius-md);
737
+ overflow: hidden;
738
+ }
739
+
740
+ .tool-item {
741
+ border-bottom: 1px solid var(--border-color-primary);
742
+ }
743
+
744
+ .tool-item:last-child {
745
+ border-bottom: none;
746
+ }
747
+
748
+ .tool-header {
749
+ width: 100%;
750
+ display: flex;
751
+ justify-content: space-between;
752
+ align-items: center;
753
+ padding: var(--size-3);
754
+ background: var(--background-fill-primary);
755
+ border: none;
756
+ cursor: pointer;
757
+ text-align: left;
758
+ }
759
+
760
+ .tool-header:hover {
761
+ background: var(--background-fill-secondary);
762
+ }
763
+
764
+ .tool-name {
765
+ font-family: var(--font-mono);
766
+ font-weight: 600;
767
+ }
768
+
769
+ .tool-arrow {
770
+ color: var(--body-text-color-subdued);
771
+ }
772
+
773
+ .tool-content {
774
+ padding: var(--size-3);
775
+ background: var(--background-fill-secondary);
776
+ }
777
+
778
+ .tool-description {
779
+ margin-bottom: var(--size-3);
780
+ color: var(--body-text-color);
781
+ }
782
+ .parameter {
783
+ margin-bottom: var(--size-2);
784
+ padding: var(--size-2);
785
+ background: var(--background-fill-primary);
786
+ border-radius: var(--radius-sm);
787
+ }
788
+
789
+ .parameter code {
790
+ font-weight: 600;
791
+ color: var(--color-accent);
792
+ }
793
+
794
+ .parameter-type {
795
+ color: var(--body-text-color-subdued);
796
+ margin-left: var(--size-1);
797
+ }
798
+
799
+ .parameter-description {
800
+ margin-top: var(--size-1);
801
+ color: var(--body-text-color);
802
+ }
422
803
  </style>
@@ -2,7 +2,7 @@
2
2
  import CopyButton from "./CopyButton.svelte";
3
3
  import { Block } from "@gradio/atoms";
4
4
 
5
- export let current_language: "python" | "javascript" | "bash";
5
+ export let current_language: "python" | "javascript" | "bash" | "mcp";
6
6
 
7
7
  let py_install = "pip install gradio_client";
8
8
  let js_install = "npm i -D @gradio/client";
@@ -9,7 +9,7 @@
9
9
  export let short_root: string;
10
10
  export let root: string;
11
11
  export let api_prefix = "";
12
- export let current_language: "python" | "javascript" | "bash";
12
+ export let current_language: "python" | "javascript" | "bash" | "mcp";
13
13
  export let username: string | null;
14
14
 
15
15
  let python_code: HTMLElement;