@llmindset/hf-mcp 0.2.40 → 0.2.42

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.
Files changed (50) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/space/commands/invoke.d.ts +6 -0
  6. package/dist/space/commands/invoke.d.ts.map +1 -0
  7. package/dist/space/commands/invoke.js +236 -0
  8. package/dist/space/commands/invoke.js.map +1 -0
  9. package/dist/space/commands/view-parameters.d.ts +3 -0
  10. package/dist/space/commands/view-parameters.d.ts.map +1 -0
  11. package/dist/space/commands/view-parameters.js +144 -0
  12. package/dist/space/commands/view-parameters.js.map +1 -0
  13. package/dist/space/space-tool.d.ts +35 -0
  14. package/dist/space/space-tool.d.ts.map +1 -0
  15. package/dist/space/space-tool.js +198 -0
  16. package/dist/space/space-tool.js.map +1 -0
  17. package/dist/space/types.d.ts +74 -0
  18. package/dist/space/types.d.ts.map +1 -0
  19. package/dist/space/types.js +24 -0
  20. package/dist/space/types.js.map +1 -0
  21. package/dist/space/utils/parameter-formatter.d.ts +5 -0
  22. package/dist/space/utils/parameter-formatter.d.ts.map +1 -0
  23. package/dist/space/utils/parameter-formatter.js +132 -0
  24. package/dist/space/utils/parameter-formatter.js.map +1 -0
  25. package/dist/space/utils/result-formatter.d.ts +4 -0
  26. package/dist/space/utils/result-formatter.d.ts.map +1 -0
  27. package/dist/space/utils/result-formatter.js +146 -0
  28. package/dist/space/utils/result-formatter.js.map +1 -0
  29. package/dist/space/utils/schema-validator.d.ts +9 -0
  30. package/dist/space/utils/schema-validator.d.ts.map +1 -0
  31. package/dist/space/utils/schema-validator.js +145 -0
  32. package/dist/space/utils/schema-validator.js.map +1 -0
  33. package/dist/space-search.d.ts.map +1 -1
  34. package/dist/space-search.js +5 -4
  35. package/dist/space-search.js.map +1 -1
  36. package/dist/tool-ids.d.ts +4 -2
  37. package/dist/tool-ids.d.ts.map +1 -1
  38. package/dist/tool-ids.js +4 -1
  39. package/dist/tool-ids.js.map +1 -1
  40. package/package.json +2 -1
  41. package/src/index.ts +1 -0
  42. package/src/space/commands/invoke.ts +335 -0
  43. package/src/space/commands/view-parameters.ts +204 -0
  44. package/src/space/space-tool.ts +252 -0
  45. package/src/space/types.ts +127 -0
  46. package/src/space/utils/parameter-formatter.ts +200 -0
  47. package/src/space/utils/result-formatter.ts +226 -0
  48. package/src/space/utils/schema-validator.ts +232 -0
  49. package/src/space-search.ts +5 -4
  50. package/src/tool-ids.ts +4 -0
@@ -0,0 +1,204 @@
1
+ import type { ToolResult } from '../../types/tool-result.js';
2
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
3
+ import { analyzeSchemaComplexity } from '../utils/schema-validator.js';
4
+ import { formatParameters, formatComplexSchemaError } from '../utils/parameter-formatter.js';
5
+
6
+ /**
7
+ * Fetches space metadata and schema to discover parameters
8
+ */
9
+ export async function viewParameters(spaceName: string, hfToken?: string): Promise<ToolResult> {
10
+ try {
11
+ // Step 1: Fetch space metadata to get subdomain
12
+ const metadata = await fetchSpaceMetadata(spaceName, hfToken);
13
+
14
+ // Step 2: Fetch schema from Gradio endpoint
15
+ const tools = await fetchGradioSchema(metadata.subdomain, metadata.private, hfToken);
16
+
17
+ // For simplicity, we'll work with the first tool
18
+ // (most Gradio spaces expose a single primary tool)
19
+ if (tools.length === 0) {
20
+ return {
21
+ formatted: `Error: No tools found for space '${spaceName}'.`,
22
+ totalResults: 0,
23
+ resultsShared: 0,
24
+ isError: true,
25
+ };
26
+ }
27
+
28
+ const tool = tools[0] as Tool;
29
+
30
+ // Step 3: Analyze schema complexity
31
+ const schemaResult = analyzeSchemaComplexity(tool);
32
+
33
+ if (!schemaResult.isSimple) {
34
+ return {
35
+ formatted: formatComplexSchemaError(spaceName, schemaResult.reason || 'Unknown reason'),
36
+ totalResults: 0,
37
+ resultsShared: 0,
38
+ isError: true,
39
+ };
40
+ }
41
+
42
+ // Step 4: Format parameters for display
43
+ const formatted = formatParameters(schemaResult, spaceName);
44
+
45
+ return {
46
+ formatted,
47
+ totalResults: schemaResult.parameters.length,
48
+ resultsShared: schemaResult.parameters.length,
49
+ };
50
+ } catch (error) {
51
+ const errorMessage = error instanceof Error ? error.message : String(error);
52
+
53
+ // Check if this is a 404 error (space not found)
54
+ const is404 = errorMessage.includes('404') || errorMessage.toLowerCase().includes('not found');
55
+
56
+ let formattedError = `Error fetching parameters for space '${spaceName}': ${errorMessage}`;
57
+
58
+ if (is404) {
59
+ formattedError += '\n\nNote: The space MUST be an MCP enabled space. Use the `space_search` tool to find MCP enabled spaces.';
60
+ }
61
+
62
+ return {
63
+ formatted: formattedError,
64
+ totalResults: 0,
65
+ resultsShared: 0,
66
+ isError: true,
67
+ };
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Fetches space metadata from HuggingFace API
73
+ */
74
+ async function fetchSpaceMetadata(
75
+ spaceName: string,
76
+ hfToken?: string
77
+ ): Promise<{ subdomain: string; private: boolean }> {
78
+ const url = `https://huggingface.co/api/spaces/${spaceName}`;
79
+ const headers: Record<string, string> = {};
80
+
81
+ if (hfToken) {
82
+ headers['Authorization'] = `Bearer ${hfToken}`;
83
+ }
84
+
85
+ const controller = new AbortController();
86
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
87
+
88
+ try {
89
+ const response = await fetch(url, {
90
+ headers,
91
+ signal: controller.signal,
92
+ });
93
+
94
+ clearTimeout(timeoutId);
95
+
96
+ if (!response.ok) {
97
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
98
+ }
99
+
100
+ const info = (await response.json()) as {
101
+ subdomain?: string;
102
+ private?: boolean;
103
+ };
104
+
105
+ if (!info.subdomain) {
106
+ throw new Error('Space does not have a subdomain');
107
+ }
108
+
109
+ return {
110
+ subdomain: info.subdomain,
111
+ private: info.private || false,
112
+ };
113
+ } finally {
114
+ clearTimeout(timeoutId);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Fetches schema from Gradio endpoint
120
+ */
121
+ async function fetchGradioSchema(subdomain: string, isPrivate: boolean, hfToken?: string): Promise<Tool[]> {
122
+ const schemaUrl = `https://${subdomain}.hf.space/gradio_api/mcp/schema`;
123
+
124
+ const headers: Record<string, string> = {
125
+ 'Content-Type': 'application/json',
126
+ };
127
+
128
+ if (isPrivate && hfToken) {
129
+ headers['X-HF-Authorization'] = `Bearer ${hfToken}`;
130
+ }
131
+
132
+ const controller = new AbortController();
133
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
134
+
135
+ try {
136
+ const response = await fetch(schemaUrl, {
137
+ method: 'GET',
138
+ headers,
139
+ signal: controller.signal,
140
+ });
141
+
142
+ clearTimeout(timeoutId);
143
+
144
+ if (!response.ok) {
145
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
146
+ }
147
+
148
+ const schemaResponse = (await response.json()) as unknown;
149
+
150
+ // Parse schema response (handle both array and object formats)
151
+ return parseSchemaResponse(schemaResponse);
152
+ } finally {
153
+ clearTimeout(timeoutId);
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Parses schema response and extracts tools
159
+ */
160
+ function parseSchemaResponse(schemaResponse: unknown): Tool[] {
161
+ const tools: Tool[] = [];
162
+
163
+ if (Array.isArray(schemaResponse)) {
164
+ // Array format: [{ name: "toolName", description: "...", inputSchema: {...} }, ...]
165
+ for (const item of schemaResponse) {
166
+ if (
167
+ typeof item === 'object' &&
168
+ item !== null &&
169
+ 'name' in item &&
170
+ 'inputSchema' in item
171
+ ) {
172
+ const itemRecord = item as Record<string, unknown>;
173
+ if (typeof itemRecord.name === 'string') {
174
+ const tool = itemRecord as { name: string; description?: string; inputSchema: unknown };
175
+ tools.push({
176
+ name: tool.name,
177
+ description: tool.description || `${tool.name} tool`,
178
+ inputSchema: {
179
+ type: 'object',
180
+ ...(tool.inputSchema as Record<string, unknown>),
181
+ },
182
+ });
183
+ }
184
+ }
185
+ }
186
+ } else if (typeof schemaResponse === 'object' && schemaResponse !== null) {
187
+ // Object format: { "toolName": { properties: {...}, required: [...] }, ... }
188
+ for (const [name, toolSchema] of Object.entries(schemaResponse)) {
189
+ if (typeof toolSchema === 'object' && toolSchema !== null) {
190
+ const schema = toolSchema as { description?: string };
191
+ tools.push({
192
+ name,
193
+ description: schema.description || `${name} tool`,
194
+ inputSchema: {
195
+ type: 'object',
196
+ ...(toolSchema as Record<string, unknown>),
197
+ },
198
+ });
199
+ }
200
+ }
201
+ }
202
+
203
+ return tools.filter((tool) => !tool.name.toLowerCase().includes('<lambda'));
204
+ }
@@ -0,0 +1,252 @@
1
+ import type { ToolResult } from '../types/tool-result.js';
2
+ import type { ServerNotification, ServerRequest } from '@modelcontextprotocol/sdk/types.js';
3
+ import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
4
+ import { spaceArgsSchema, OPERATION_NAMES, type OperationName, type SpaceArgs, type InvokeResult } from './types.js';
5
+ import { viewParameters } from './commands/view-parameters.js';
6
+ import { invokeSpace } from './commands/invoke.js';
7
+
8
+ // Re-export types (including InvokeResult for external use)
9
+ export * from './types.js';
10
+
11
+ /**
12
+ * Usage instructions when tool is called with no operation
13
+ */
14
+ const USAGE_INSTRUCTIONS = `# Gradio Space Interaction
15
+
16
+ Dynamically interact with any Gradio MCP Space. View parameter schemas or invoke spaces with custom parameters.
17
+
18
+ ## Supported Schema Types
19
+
20
+ ✅ **Simple types** (supported):
21
+ - Strings, numbers, booleans
22
+ - Enums (predefined value sets)
23
+ - Arrays of primitives
24
+ - Shallow objects (one level deep)
25
+ - FileData (as URL strings)
26
+
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 manage their settings.
34
+
35
+ ## Available Operations
36
+
37
+ ### view_parameters
38
+ Display the parameter schema for a space's first tool.
39
+
40
+ **Example:**
41
+ \`\`\`json
42
+ {
43
+ "operation": "view_parameters",
44
+ "space_name": "evalstate/FLUX1_schnell"
45
+ }
46
+ \`\`\`
47
+
48
+ ### invoke
49
+ Execute a space's first tool with provided parameters.
50
+
51
+ **Example:**
52
+ \`\`\`json
53
+ {
54
+ "operation": "invoke",
55
+ "space_name": "evalstate/FLUX1_schnell",
56
+ "parameters": "{\\"prompt\\": \\"a cute cat\\", \\"num_steps\\": 4}"
57
+ }
58
+ \`\`\`
59
+
60
+ ## Workflow
61
+
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)
65
+
66
+ ## File Handling
67
+
68
+ For parameters that accept files (FileData types):
69
+ - Provide a publicly accessible URL (http:// or https://)
70
+ - Example: \`{"image": "https://example.com/photo.jpg"}\`
71
+ - To upload local files, use the dedicated gr_* prefixed tool for that space
72
+
73
+ ## Tips
74
+
75
+ - The tool automatically applies default values for optional parameters
76
+ - Unknown parameters generate warnings but are still passed through (permissive inputs)
77
+ - Enum parameters show all allowed values in view_parameters
78
+ - Required parameters are clearly marked and validated
79
+ `;
80
+
81
+ /**
82
+ * Space tool configuration
83
+ */
84
+ export const DYNAMIC_SPACE_TOOL_CONFIG = {
85
+ name: 'dynamic_space',
86
+ 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). ' +
89
+ 'Call with no operation for full usage instructions.',
90
+ schema: spaceArgsSchema,
91
+ annotations: {
92
+ title: 'Gradio Space Interaction',
93
+ readOnlyHint: false,
94
+ openWorldHint: true,
95
+ },
96
+ } as const;
97
+
98
+ /**
99
+ * Space tool implementation
100
+ */
101
+ export class SpaceTool {
102
+ private hfToken?: string;
103
+
104
+ constructor(hfToken?: string) {
105
+ this.hfToken = hfToken;
106
+ }
107
+
108
+ /**
109
+ * Execute a space operation
110
+ * Returns InvokeResult (with raw MCP content) for invoke operation,
111
+ * or ToolResult (with formatted text) for other operations
112
+ */
113
+ async execute(
114
+ params: SpaceArgs,
115
+ extra?: RequestHandlerExtra<ServerRequest, ServerNotification>
116
+ ): Promise<InvokeResult | ToolResult> {
117
+ const requestedOperation = params.operation;
118
+
119
+ // If no operation provided, return usage instructions
120
+ if (!requestedOperation) {
121
+ return {
122
+ formatted: USAGE_INSTRUCTIONS,
123
+ totalResults: 1,
124
+ resultsShared: 1,
125
+ };
126
+ }
127
+
128
+ // Validate operation
129
+ const normalizedOperation = requestedOperation.toLowerCase();
130
+ if (!isOperationName(normalizedOperation)) {
131
+ return {
132
+ formatted: `Unknown operation: "${requestedOperation}"
133
+ Available operations: ${OPERATION_NAMES.join(', ')}
134
+
135
+ Call this tool with no operation for full usage instructions.`,
136
+ totalResults: 0,
137
+ resultsShared: 0,
138
+ isError: true,
139
+ };
140
+ }
141
+
142
+ // Execute operation
143
+ try {
144
+ switch (normalizedOperation) {
145
+ case 'view_parameters':
146
+ return await this.handleViewParameters(params);
147
+
148
+ case 'invoke':
149
+ return await this.handleInvoke(params, extra);
150
+
151
+ default:
152
+ return {
153
+ formatted: `Unknown operation: "${requestedOperation}"`,
154
+ totalResults: 0,
155
+ resultsShared: 0,
156
+ isError: true,
157
+ };
158
+ }
159
+ } catch (error) {
160
+ const errorMessage = error instanceof Error ? error.message : String(error);
161
+ return {
162
+ formatted: `Error executing ${requestedOperation}: ${errorMessage}`,
163
+ totalResults: 0,
164
+ resultsShared: 0,
165
+ isError: true,
166
+ };
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Handle view_parameters operation
172
+ */
173
+ private async handleViewParameters(params: SpaceArgs): Promise<ToolResult> {
174
+ if (!params.space_name) {
175
+ return {
176
+ formatted: `Error: Missing required parameter: "space_name"
177
+
178
+ Example:
179
+ \`\`\`json
180
+ {
181
+ "operation": "view_parameters",
182
+ "space_name": "username/space-name"
183
+ }
184
+ \`\`\``,
185
+ totalResults: 0,
186
+ resultsShared: 0,
187
+ isError: true,
188
+ };
189
+ }
190
+
191
+ return await viewParameters(params.space_name, this.hfToken);
192
+ }
193
+
194
+ /**
195
+ * Handle invoke operation
196
+ * Returns either InvokeResult (with raw MCP content) or ToolResult (error messages)
197
+ */
198
+ private async handleInvoke(
199
+ params: SpaceArgs,
200
+ extra?: RequestHandlerExtra<ServerRequest, ServerNotification>
201
+ ): Promise<InvokeResult | ToolResult> {
202
+ // Validate required parameters
203
+ if (!params.space_name) {
204
+ return {
205
+ formatted: `Error: Missing required parameter: "space_name"
206
+
207
+ Example:
208
+ \`\`\`json
209
+ {
210
+ "operation": "invoke",
211
+ "space_name": "username/space-name",
212
+ "parameters": "{\\"param1\\": \\"value1\\"}"
213
+ }
214
+ \`\`\``,
215
+ totalResults: 0,
216
+ resultsShared: 0,
217
+ isError: true,
218
+ };
219
+ }
220
+
221
+ if (!params.parameters) {
222
+ return {
223
+ formatted: `Error: Missing required parameter: "parameters"
224
+
225
+ The "parameters" field must be a JSON object string containing the space parameters.
226
+
227
+ Example:
228
+ \`\`\`json
229
+ {
230
+ "operation": "invoke",
231
+ "space_name": "${params.space_name}",
232
+ "parameters": "{\\"param1\\": \\"value1\\", \\"param2\\": 42}"
233
+ }
234
+ \`\`\`
235
+
236
+ Use "view_parameters" to see what parameters this space accepts.`,
237
+ totalResults: 0,
238
+ resultsShared: 0,
239
+ isError: true,
240
+ };
241
+ }
242
+
243
+ return await invokeSpace(params.space_name, params.parameters, this.hfToken, extra);
244
+ }
245
+ }
246
+
247
+ /**
248
+ * Type guard for operation names
249
+ */
250
+ function isOperationName(value: string): value is OperationName {
251
+ return (OPERATION_NAMES as readonly string[]).includes(value);
252
+ }
@@ -0,0 +1,127 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Operations supported by the space tool
5
+ */
6
+ export const OPERATION_NAMES = ['view_parameters', 'invoke'] as const;
7
+ export type OperationName = (typeof OPERATION_NAMES)[number];
8
+
9
+ /**
10
+ * Zod schema for operation arguments
11
+ */
12
+ export const spaceArgsSchema = z.object({
13
+ operation: z
14
+ .enum(OPERATION_NAMES)
15
+ .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")'),
18
+ parameters: z.string().optional().describe('For invoke operation: JSON object string of parameters'),
19
+ });
20
+
21
+ export type SpaceArgs = z.infer<typeof spaceArgsSchema>;
22
+
23
+ /**
24
+ * Parameter information extracted from schema
25
+ */
26
+ export interface ParameterInfo {
27
+ name: string;
28
+ type: string;
29
+ description?: string;
30
+ required: boolean;
31
+ default?: unknown;
32
+ enum?: unknown[];
33
+ isFileData?: boolean;
34
+ complexType?: string; // Reason why the type is complex
35
+ }
36
+
37
+ /**
38
+ * Result of schema complexity analysis
39
+ */
40
+ export interface SchemaComplexityResult {
41
+ isSimple: boolean;
42
+ reason?: string; // Reason if not simple
43
+ parameters: ParameterInfo[];
44
+ toolName: string;
45
+ toolDescription?: string;
46
+ }
47
+
48
+ /**
49
+ * Result of parameter processing
50
+ */
51
+ export interface ProcessParametersResult {
52
+ valid: boolean;
53
+ parameters?: Record<string, unknown>;
54
+ error?: string;
55
+ warnings?: string[];
56
+ }
57
+
58
+ /**
59
+ * JSON Schema property definition
60
+ */
61
+ export interface JsonSchemaProperty {
62
+ type?: string;
63
+ title?: string;
64
+ description?: string;
65
+ default?: unknown;
66
+ enum?: unknown[];
67
+ format?: string;
68
+ properties?: Record<string, JsonSchemaProperty>;
69
+ items?: JsonSchemaProperty;
70
+ required?: string[];
71
+ [key: string]: unknown;
72
+ }
73
+
74
+ /**
75
+ * JSON Schema definition
76
+ */
77
+ export interface JsonSchema {
78
+ type?: string;
79
+ properties?: Record<string, JsonSchemaProperty>;
80
+ required?: string[];
81
+ description?: string;
82
+ [key: string]: unknown;
83
+ }
84
+
85
+ /**
86
+ * File input help message constant
87
+ */
88
+ 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.';
91
+
92
+ /**
93
+ * Check if a property is a FileData type
94
+ */
95
+ export function isFileDataProperty(prop: JsonSchemaProperty): boolean {
96
+ return (
97
+ prop.title === 'ImageData' ||
98
+ prop.title === 'FileData' ||
99
+ (prop.format?.includes('http') && prop.format?.includes('file')) ||
100
+ false
101
+ );
102
+ }
103
+
104
+ /**
105
+ * Check if a schema contains file data
106
+ */
107
+ export function hasFileData(schema: JsonSchema): boolean {
108
+ if (!schema.properties) return false;
109
+
110
+ return Object.values(schema.properties).some((prop) => isFileDataProperty(prop));
111
+ }
112
+
113
+ /**
114
+ * Extended result type for invoke operation that includes raw MCP result
115
+ * This allows the space tool to return structured content blocks instead of formatted text
116
+ */
117
+ export interface InvokeResult {
118
+ result: {
119
+ content: unknown[];
120
+ isError?: boolean;
121
+ [key: string]: unknown;
122
+ };
123
+ warnings: string[];
124
+ totalResults: number;
125
+ resultsShared: number;
126
+ isError?: boolean;
127
+ }