Skip to content

Commit 4ae5ce9

Browse files
authored
Merge pull request #120 from huggingface/claude/add-dynamic-space-discover-01Ke3Lm8scu2ZYHEMGLRiKL2
Add discover operation to dynamic_space tool
2 parents 54af3c5 + 9da5ac6 commit 4ae5ce9

File tree

4 files changed

+203
-22
lines changed

4 files changed

+203
-22
lines changed

packages/app/src/shared/bouquet-presets.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import {
55
USE_SPACE_TOOL_ID,
66
HF_JOBS_TOOL_ID,
77
DYNAMIC_SPACE_TOOL_ID,
8-
SPACE_SEARCH_TOOL_ID,
98
} from '@llmindset/hf-mcp';
109
import type { AppSettings } from './settings.js';
1110
import { README_INCLUDE_FLAG, GRADIO_IMAGE_FILTER_FLAG } from './behavior-flags.js';
@@ -53,7 +52,7 @@ export const BOUQUETS: Record<string, AppSettings> = {
5352
spaceTools: [],
5453
},
5554
dynamic_space: {
56-
builtInTools: [SPACE_SEARCH_TOOL_ID, DYNAMIC_SPACE_TOOL_ID],
55+
builtInTools: [DYNAMIC_SPACE_TOOL_ID],
5756
spaceTools: [],
5857
},
5958
};
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import type { ToolResult } from '../../types/tool-result.js';
2+
import { SpaceSearchTool, type SpaceSearchResult } from '../../space-search.js';
3+
import { escapeMarkdown } from '../../utilities.js';
4+
5+
// Default number of results to return
6+
const DEFAULT_RESULTS_LIMIT = 10;
7+
8+
/**
9+
* Prompt configuration for discover operation
10+
* These prompts can be easily tweaked to adjust the search behavior
11+
*/
12+
const DISCOVER_PROMPTS = {
13+
// Task hints shown when called with blank query
14+
TASK_HINTS: `Here are some examples of tasks that dynamic spaces can perform:
15+
16+
- Image Generation
17+
- Video Generation
18+
- Text Generation
19+
- Visual QA
20+
- Language Translation
21+
- Speech Synthesis
22+
- 3D Modeling
23+
- Object Detection
24+
- Text Analysis
25+
- Image Editing
26+
- Code Generation
27+
- Question Answering
28+
- Data Visualization
29+
- Voice Cloning
30+
- Background Removal
31+
- OCR
32+
- Image Captioning
33+
- Sentiment Analysis
34+
- Music Generation
35+
- Style Transfer
36+
37+
To discover MCP-enabled Spaces for a specific task, call this operation with a search query:
38+
39+
**Example:**
40+
\`\`\`json
41+
{
42+
"operation": "discover",
43+
"search_query": "image generation",
44+
"limit": 10
45+
}
46+
\`\`\``,
47+
48+
// Header for search results
49+
RESULTS_HEADER: (query: string, showing: number, total: number) => {
50+
const showingText = showing < total ? `Showing ${showing} of ${total} results` : `All ${showing} results`;
51+
return `# MCP Space Discovery Results for "${query}" (${showingText})
52+
53+
These MCP-enabled Spaces can be invoked using the \`dynamic_space\` tool.
54+
Use \`"operation": "view_parameters"\` to inspect a space's parameters before invoking.
55+
56+
`;
57+
},
58+
59+
// No results message
60+
NO_RESULTS: (query: string) =>
61+
`No MCP-enabled Spaces found for "${query}".
62+
63+
Try:
64+
- Broader search terms (e.g., "image generation" instead of specific model names)
65+
- Task-focused queries (e.g., "text generation", "object detection")
66+
- Different task categories (e.g., "video generation", "image classification")`,
67+
};
68+
69+
/**
70+
* Discovers MCP-enabled Spaces based on search criteria
71+
*
72+
* @param searchQuery - The search query or task category
73+
* @param limit - Maximum number of results to return
74+
* @param hfToken - Optional HuggingFace API token
75+
* @returns Formatted search results
76+
*/
77+
export async function discoverSpaces(
78+
searchQuery?: string,
79+
limit: number = DEFAULT_RESULTS_LIMIT,
80+
hfToken?: string
81+
): Promise<ToolResult> {
82+
// Return task hints when called with blank query
83+
if (!searchQuery || searchQuery.trim() === '') {
84+
return {
85+
formatted: DISCOVER_PROMPTS.TASK_HINTS,
86+
totalResults: 0,
87+
resultsShared: 0,
88+
};
89+
}
90+
91+
try {
92+
// Use SpaceSearchTool to search for MCP-enabled spaces only
93+
const searchTool = new SpaceSearchTool(hfToken);
94+
const { results, totalCount } = await searchTool.search(
95+
searchQuery,
96+
limit,
97+
true // mcp = true (only MCP-enabled spaces)
98+
);
99+
100+
// Format and return results
101+
return formatDiscoverResults(searchQuery, results, totalCount);
102+
} catch (error) {
103+
const errorMessage = error instanceof Error ? error.message : String(error);
104+
return {
105+
formatted: `Error discovering spaces: ${errorMessage}`,
106+
totalResults: 0,
107+
resultsShared: 0,
108+
isError: true,
109+
};
110+
}
111+
}
112+
113+
/**
114+
* Formats discover results as a markdown table
115+
* Note: Author column is omitted as it's superfluous for invocation purposes
116+
* Duplication is OK for the mean time; space_search will be rolled in to a general tool
117+
*/
118+
function formatDiscoverResults(query: string, results: SpaceSearchResult[], totalCount: number): ToolResult {
119+
if (results.length === 0) {
120+
return {
121+
formatted: DISCOVER_PROMPTS.NO_RESULTS(query),
122+
totalResults: 0,
123+
resultsShared: 0,
124+
};
125+
}
126+
127+
let markdown = DISCOVER_PROMPTS.RESULTS_HEADER(query, results.length, totalCount);
128+
129+
// Table header (without Author column)
130+
markdown += '| Space | Description | Space ID | Category | Likes | Trending | Relevance |\n';
131+
markdown += '|-------|-------------|----------|----------|-------|----------|----------|\n';
132+
133+
// Table rows
134+
for (const result of results) {
135+
const title = result.title || 'Untitled';
136+
const description = result.shortDescription || result.ai_short_description || 'No description';
137+
const id = result.id || '';
138+
const emoji = result.emoji ? escapeMarkdown(result.emoji) + ' ' : '';
139+
const relevance = result.semanticRelevancyScore ? (result.semanticRelevancyScore * 100).toFixed(1) + '%' : 'N/A';
140+
141+
markdown +=
142+
`| ${emoji}[${escapeMarkdown(title)}](https://hf.co/spaces/${id}) ` +
143+
`| ${escapeMarkdown(description)} ` +
144+
`| \`${escapeMarkdown(id)}\` ` +
145+
`| \`${escapeMarkdown(result.ai_category ?? '-')}\` ` +
146+
`| ${escapeMarkdown(result.likes?.toString() ?? '-')} ` +
147+
`| ${escapeMarkdown(result.trendingScore?.toString() ?? '-')} ` +
148+
`| ${relevance} |\n`;
149+
}
150+
151+
return {
152+
formatted: markdown,
153+
totalResults: totalCount,
154+
resultsShared: results.length,
155+
};
156+
}

packages/mcp/src/space/space-tool.ts

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ToolResult } from '../types/tool-result.js';
22
import type { ServerNotification, ServerRequest } from '@modelcontextprotocol/sdk/types.js';
33
import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
44
import { spaceArgsSchema, OPERATION_NAMES, type OperationName, type SpaceArgs, type InvokeResult } from './types.js';
5+
import { discoverSpaces } from './commands/discover.js';
56
import { viewParameters } from './commands/view-parameters.js';
67
import { invokeSpace } from './commands/invoke.js';
78

@@ -13,7 +14,7 @@ export * from './types.js';
1314
*/
1415
const USAGE_INSTRUCTIONS = `# Gradio Space Interaction
1516
16-
Dynamically interact with any Gradio MCP Space. View parameter schemas or invoke spaces with custom parameters.
17+
Dynamically interact with any Gradio MCP Space. Discover dynamic spaces, view space parameter schemas, and invoke spaces.
1718
1819
## Supported Schema Types
1920
@@ -24,16 +25,22 @@ Dynamically interact with any Gradio MCP Space. View parameter schemas or invoke
2425
- Shallow objects (one level deep)
2526
- FileData (as URL strings)
2627
27-
❌ **Complex types** (not supported):
28-
- Deeply nested objects (2+ levels)
29-
- Arrays of objects
30-
- Union types
31-
- Recursive schemas
32-
33-
For spaces with complex schemas, direct the user to huggingface.co/settings/mcp to add the space via settings panel.
28+
To use spaces with complex schemas, add them from huggingface.co/settings/mcp.
3429
3530
## Available Operations
3631
32+
### discover
33+
Find MCP-enabled Spaces for available for invocation based on task-focused or semantic searches.
34+
35+
**Example:**
36+
\`\`\`json
37+
{
38+
"operation": "discover",
39+
"search_query": "image generation",
40+
"limit": 10
41+
}
42+
\`\`\`
43+
3744
### view_parameters
3845
Display the parameter schema for a space's first tool.
3946
@@ -59,9 +66,9 @@ Execute a space's first tool with provided parameters.
5966
6067
## Workflow
6168
62-
1. **Discover parameters** - Use \`view_parameters\` to see what a space accepts
63-
2. **Invoke the space** - Use \`invoke\` with the required parameters
64-
3. **Review results** - Get formatted output (text, images, resources)
69+
1. **Discover Spaces** - Use \`discover\` to find MCP-enabled spaces for your task
70+
2. **Inspect Parameters** - Use \`view_parameters\` to see what a space accepts
71+
3. **Invoke the Space** - Use \`invoke\` with the required parameters
6572
6673
## File Handling
6774
@@ -72,6 +79,7 @@ For parameters that accept files (FileData types):
7279
7380
## Tips
7481
82+
- Focus searches on specific tasks (e.g., "video generation", "object detection")
7583
- The tool automatically applies default values for optional parameters
7684
- Unknown parameters generate warnings but are still passed through (permissive inputs)
7785
- Enum parameters show all allowed values in view_parameters
@@ -84,8 +92,7 @@ For parameters that accept files (FileData types):
8492
export const DYNAMIC_SPACE_TOOL_CONFIG = {
8593
name: 'dynamic_space',
8694
description:
87-
'Dynamically interact with Gradio MCP Spaces . View parameter schemas or invoke spaces with custom parameters. ' +
88-
'Supports simple parameter types (strings, numbers, booleans, arrays, enums, shallow objects). ' +
95+
'Discover (semantic/task search), inspect (view parameter schema) and dynamically invoke Gradio MCP Spaces to perform various ML Tasks. ' +
8996
'Call with no operation for full usage instructions.',
9097
schema: spaceArgsSchema,
9198
annotations: {
@@ -142,6 +149,9 @@ Call this tool with no operation for full usage instructions.`,
142149
// Execute operation
143150
try {
144151
switch (normalizedOperation) {
152+
case 'discover':
153+
return await this.handleDiscover(params);
154+
145155
case 'view_parameters':
146156
return await this.handleViewParameters(params);
147157

@@ -167,6 +177,13 @@ Call this tool with no operation for full usage instructions.`,
167177
}
168178
}
169179

180+
/**
181+
* Handle discover operation
182+
*/
183+
private async handleDiscover(params: SpaceArgs): Promise<ToolResult> {
184+
return await discoverSpaces(params.search_query, params.limit, this.hfToken);
185+
}
186+
170187
/**
171188
* Handle view_parameters operation
172189
*/

packages/mcp/src/space/types.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,28 @@ import { z } from 'zod';
33
/**
44
* Operations supported by the space tool
55
*/
6-
export const OPERATION_NAMES = ['view_parameters', 'invoke'] as const;
6+
export const OPERATION_NAMES = ['discover', 'view_parameters', 'invoke'] as const;
77
export type OperationName = (typeof OPERATION_NAMES)[number];
88

99
/**
1010
* Zod schema for operation arguments
1111
*/
1212
export const spaceArgsSchema = z.object({
13-
operation: z
14-
.enum(OPERATION_NAMES)
13+
operation: z.enum(OPERATION_NAMES).optional().describe('Operation to execute.'),
14+
space_name: z
15+
.string()
1516
.optional()
16-
.describe('Operation to execute. Valid values: "view_parameters", "invoke"'),
17-
space_name: z.string().optional().describe('The Hugging Face space ID (format: "username/space-name")'),
17+
.describe(
18+
'The Hugging Face space ID (format: "username/space-name"). Required for view_parameters and invoke operations.'
19+
),
1820
parameters: z.string().optional().describe('For invoke operation: JSON object string of parameters'),
21+
search_query: z
22+
.string()
23+
.optional()
24+
.describe(
25+
'For discover operation: Search query or task category (e.g. "Image Generation", "OCR", "FLUX image generation") to find MCP-enabled Spaces. Call with blank query to see task hints.'
26+
),
27+
limit: z.number().optional().describe('For discover operation: Maximum number of results to return (default: 10)'),
1928
});
2029

2130
export type SpaceArgs = z.infer<typeof spaceArgsSchema>;
@@ -86,8 +95,8 @@ export interface JsonSchema {
8695
* File input help message constant
8796
*/
8897
export const FILE_INPUT_HELP_MESSAGE =
89-
'Provide a publicly accessible URL (http:// or https://) pointing to the file. ' +
90-
'To upload local files, use the dedicated gr_* prefixed tool for this space, which supports file upload.';
98+
'Provide a publicly accessible URL (http:// or https://) for the Gradio file input. ' +
99+
"Content previuosly generated by 'invoke' is usable, as well as Hub Repository URLs. ";
91100

92101
/**
93102
* Check if a property is a FileData type

0 commit comments

Comments
 (0)