@llmindset/hf-mcp 0.2.52 → 0.2.54

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 (44) hide show
  1. package/dist/index.d.ts +3 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +3 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/space/commands/discover.d.ts.map +1 -1
  6. package/dist/space/commands/discover.js +2 -1
  7. package/dist/space/commands/discover.js.map +1 -1
  8. package/dist/space/commands/dynamic-find.d.ts.map +1 -1
  9. package/dist/space/commands/dynamic-find.js +2 -1
  10. package/dist/space/commands/dynamic-find.js.map +1 -1
  11. package/dist/space/commands/invoke.d.ts.map +1 -1
  12. package/dist/space/commands/invoke.js +14 -110
  13. package/dist/space/commands/invoke.js.map +1 -1
  14. package/dist/space/{space-tool.d.ts → dynamic-space-tool.d.ts} +2 -2
  15. package/dist/space/dynamic-space-tool.d.ts.map +1 -0
  16. package/dist/space/{space-tool.js → dynamic-space-tool.js} +29 -45
  17. package/dist/space/dynamic-space-tool.js.map +1 -0
  18. package/dist/space/types.d.ts +3 -1
  19. package/dist/space/types.d.ts.map +1 -1
  20. package/dist/space/types.js +14 -9
  21. package/dist/space/types.js.map +1 -1
  22. package/dist/space/utils/gradio-caller.d.ts +14 -0
  23. package/dist/space/utils/gradio-caller.d.ts.map +1 -0
  24. package/dist/space/utils/gradio-caller.js +138 -0
  25. package/dist/space/utils/gradio-caller.js.map +1 -0
  26. package/dist/space/utils/gradio-schema.d.ts +13 -0
  27. package/dist/space/utils/gradio-schema.d.ts.map +1 -0
  28. package/dist/space/utils/gradio-schema.js +44 -0
  29. package/dist/space/utils/gradio-schema.js.map +1 -0
  30. package/dist/space/utils/parameter-formatter.js +2 -2
  31. package/dist/space/utils/parameter-formatter.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/index.ts +3 -1
  34. package/src/space/commands/discover.ts +2 -1
  35. package/src/space/commands/dynamic-find.ts +2 -1
  36. package/src/space/commands/invoke.ts +13 -147
  37. package/src/space/{space-tool.ts → dynamic-space-tool.ts} +29 -44
  38. package/src/space/types.ts +24 -18
  39. package/src/space/utils/gradio-caller.ts +204 -0
  40. package/src/space/utils/gradio-schema.ts +74 -0
  41. package/src/space/utils/parameter-formatter.ts +2 -2
  42. package/test/gradio-caller.test.ts +77 -0
  43. package/dist/space/space-tool.d.ts.map +0 -1
  44. package/dist/space/space-tool.js.map +0 -1
@@ -9,6 +9,8 @@ import {
9
9
  isDynamicSpaceMode,
10
10
  getOperationNames,
11
11
  getSpaceArgsSchema,
12
+ VIEW_PARAMETERS,
13
+ FILE_HANDLING_TEXT,
12
14
  } from './types.js';
13
15
  import { findSpaces } from './commands/dynamic-find.js';
14
16
  import { discoverSpaces } from './commands/discover.js';
@@ -24,11 +26,6 @@ export * from './types.js';
24
26
  const USAGE_INSTRUCTIONS = `# Gradio Space Interaction
25
27
 
26
28
  Dynamically interact with any Gradio MCP Space. Find spaces, view space parameter schemas, and invoke spaces.
27
-
28
- ## Supported Schema Types
29
-
30
- ✅ **Simple types** (supported):
31
- - Strings, numbers, booleans
32
29
  - Enums (predefined value sets)
33
30
  - Arrays of primitives
34
31
  - Shallow objects (one level deep)
@@ -50,13 +47,13 @@ Find MCP-enabled Spaces for available for invocation based on task-focused or se
50
47
  }
51
48
  \`\`\`
52
49
 
53
- ### view_parameters
50
+ ### ${VIEW_PARAMETERS}
54
51
  Display the parameter schema for a space's first tool.
55
52
 
56
53
  **Example:**
57
54
  \`\`\`json
58
55
  {
59
- "operation": "view_parameters",
56
+ "operation": "${VIEW_PARAMETERS}",
60
57
  "space_name": "evalstate/FLUX1_schnell"
61
58
  }
62
59
  \`\`\`
@@ -76,32 +73,31 @@ Execute a space's first tool with provided parameters.
76
73
  ## Workflow
77
74
 
78
75
  1. **Find Spaces** - Use \`find\` to find MCP-enabled spaces for your task
79
- 2. **Inspect Parameters** - Use \`view_parameters\` to see what a space accepts
76
+ 2. **Inspect Parameters** - Use \`${VIEW_PARAMETERS}\` to see what a space accepts
80
77
  3. **Invoke the Space** - Use \`invoke\` with the required parameters
81
-
82
- ## File Handling
83
-
84
78
  For parameters that accept files (FileData types):
85
79
  - Provide a publicly accessible URL (http:// or https://)
86
80
  - Example: \`{"image": "https://example.com/photo.jpg"}\`
87
- - Outputs from one tool may be used as inputs to another
88
-
89
- ## Tips
90
-
91
- - Focus searches on specific tasks (e.g., "video generation", "object detection")
92
81
  - The tool automatically applies default values for optional parameters
93
82
  - Unknown parameters generate warnings but are still passed through (permissive inputs)
94
- - Enum parameters show all allowed values in view_parameters
83
+ - Enum parameters show all allowed values in ${VIEW_PARAMETERS} output
95
84
  - Required parameters are clearly marked and validated
96
85
  `;
97
86
 
98
87
  /**
99
88
  * Usage instructions for dynamic mode (when DYNAMIC_SPACE_DATA is set)
100
- * Simplified instructions focusing on discover/view_parameters/invoke workflow
101
89
  */
102
- const DYNAMIC_USAGE_INSTRUCTIONS = `# Gradio Space Interaction
90
+ const DYNAMIC_USAGE_INSTRUCTIONS = `# Hugging Face Space Dynamic Use
91
+
92
+ Perform Tasks using Hugging Face Spaces.
93
+
94
+ ## Workflow
95
+
96
+ 1. **Discover Taks and Spaces** - Use \`discover\` operation to see available spaces
97
+ 2. **View Parameters** - Use \`${VIEW_PARAMETERS}\` operation to inspect parameter schema
98
+ 3. **Invoke the Space** - Use \`invoke\` operation with the necessary parameters
103
99
 
104
- Dynamically use Gradio MCP Spaces. Discover available spaces, view their parameter schemas, and invoke them. Use "discover" to find recommended spaces for tasks.
100
+ ${FILE_HANDLING_TEXT}
105
101
 
106
102
  ## Available Operations
107
103
 
@@ -115,19 +111,19 @@ List recommended spaces and their categories.
115
111
  }
116
112
  \`\`\`
117
113
 
118
- ### view_parameters
119
- Display the parameter schema for a space's first tool.
114
+ ### ${VIEW_PARAMETERS}
115
+ Display the parameter schema for the Space.
120
116
 
121
117
  **Example:**
122
118
  \`\`\`json
123
119
  {
124
- "operation": "view_parameters",
120
+ "operation": "${VIEW_PARAMETERS}",
125
121
  "space_name": "evalstate/FLUX1_schnell"
126
122
  }
127
123
  \`\`\`
128
124
 
129
125
  ### invoke
130
- Execute a space's first tool with provided parameters.
126
+ Execute a Task on a Space.
131
127
 
132
128
  **Example:**
133
129
  \`\`\`json
@@ -138,18 +134,7 @@ Execute a space's first tool with provided parameters.
138
134
  }
139
135
  \`\`\`
140
136
 
141
- ## Workflow
142
-
143
- 1. **Discover Spaces** - Use \`discover\` to see available spaces
144
- 2. **Inspect Parameters** - Use \`view_parameters\` to see what a space accepts
145
- 3. **Invoke the Space** - Use \`invoke\` with the required parameters
146
137
 
147
- ## File Handling
148
-
149
- For parameters that accept files (FileData types):
150
- - Provide a publicly accessible URL (http:// or https://)
151
- - Example: \`{"image": "https://example.com/photo.jpg"}\`
152
- - Output url's from one tool may be used as inputs to another.
153
138
  `;
154
139
 
155
140
  /**
@@ -173,13 +158,13 @@ export function getDynamicSpaceToolConfig(): {
173
158
  return {
174
159
  name: 'dynamic_space',
175
160
  description: dynamicMode
176
- ? 'Discover, inspect (view parameter schema) and dynamically invoke Gradio MCP Spaces to conduct ML Tasks including Image Generation, Background Removal, Text to Speech and more ' +
177
- 'Call with no operation for full usage instructions.'
178
- : 'Find (semantic/task search), inspect (view parameter schema) and dynamically invoke Gradio MCP Spaces. ' +
179
- 'Call with no operation for full usage instructions.',
161
+ ? 'Perform Tasks with Hugging Face Spaces. Use "discover" to view available Tasks. Examples are Image Generation/Editing, Background Removal, Text to Speech, OCR and many more. ' +
162
+ 'Call with no arguments for full usage instructions.'
163
+ : 'Find (semantic/task search), inspect (view parameter schema) and dynamically invoke Hugging Face Spaces. ' +
164
+ 'Call with no arguments for full usage instructions.',
180
165
  schema: getSpaceArgsSchema(),
181
166
  annotations: {
182
- title: 'Dynamically use Gradio Applications',
167
+ title: 'Dynamically use Hugging Face Spaces',
183
168
  readOnlyHint: false,
184
169
  openWorldHint: true,
185
170
  },
@@ -192,11 +177,11 @@ export function getDynamicSpaceToolConfig(): {
192
177
  export const DYNAMIC_SPACE_TOOL_CONFIG = {
193
178
  name: 'dynamic_space',
194
179
  description:
195
- 'Find (semantic/task search), inspect (view parameter schema) and dynamically invoke Gradio MCP Spaces. ' +
180
+ 'Find (semantic/task search), inspect (view parameter schema) and dynamically invoke Hugging Face Spaces. ' +
196
181
  'Call with no operation for full usage instructions.',
197
182
  schema: spaceArgsSchema,
198
183
  annotations: {
199
- title: 'Dynamically use Gradio Applications',
184
+ title: 'Dynamically use Hugging Face Spaces',
200
185
  readOnlyHint: false,
201
186
  openWorldHint: true,
202
187
  },
@@ -306,7 +291,7 @@ Call this tool with no operation for full usage instructions.`,
306
291
  Example:
307
292
  \`\`\`json
308
293
  {
309
- "operation": "view_parameters",
294
+ "operation": "${VIEW_PARAMETERS}",
310
295
  "space_name": "username/space-name"
311
296
  }
312
297
  \`\`\``,
@@ -361,7 +346,7 @@ Example:
361
346
  }
362
347
  \`\`\`
363
348
 
364
- Use "view_parameters" to see what parameters this space accepts.`,
349
+ Use "${VIEW_PARAMETERS}" to see what parameters this space accepts.`,
365
350
  totalResults: 0,
366
351
  resultsShared: 0,
367
352
  isError: true,
@@ -5,11 +5,28 @@ import { z } from 'zod';
5
5
  * Standard mode: find, view_parameters, invoke
6
6
  * Dynamic mode (DYNAMIC_SPACE_DATA): discover, view_parameters, invoke
7
7
  */
8
- export const OPERATION_NAMES = ['find', 'view_parameters', 'invoke'] as const;
9
- export const DYNAMIC_OPERATION_NAMES = ['discover', 'view_parameters', 'invoke'] as const;
8
+
9
+ export const VIEW_PARAMETERS = 'view_parameters';
10
+ export const OPERATION_NAMES = ['find', VIEW_PARAMETERS, 'invoke'] as const;
11
+ export const DYNAMIC_OPERATION_NAMES = ['discover', VIEW_PARAMETERS, 'invoke'] as const;
10
12
  export type OperationName = (typeof OPERATION_NAMES)[number];
11
13
  export type DynamicOperationName = (typeof DYNAMIC_OPERATION_NAMES)[number];
12
14
 
15
+ /**
16
+ * File input help message constant
17
+ */
18
+ export const FILE_INPUT_HELP_MESSAGE =
19
+ 'Provide a publicly accessible URL (http:// or https://) for the file input. ' +
20
+ "Previously generated URL's are usable. ";
21
+
22
+ /**
23
+ * File handling instructions for user-facing documentation
24
+ */
25
+ export const FILE_HANDLING_TEXT = `For parameters that accept files (FileData, Images, file URL types):
26
+ - Provide a publicly accessible URL (http:// or https://)
27
+ - Example: \`{"image": "https://example.com/photo.jpg"}\`
28
+ - URL's generated by one Space can be used as inputs to another Space.`;
29
+
13
30
  /**
14
31
  * Check if dynamic space mode is enabled
15
32
  */
@@ -32,10 +49,8 @@ export const spaceArgsSchema = z.object({
32
49
  space_name: z
33
50
  .string()
34
51
  .optional()
35
- .describe(
36
- 'The Hugging Face space ID (format: "username/space-name"). Required for view_parameters and invoke operations.'
37
- ),
38
- parameters: z.string().optional().describe('For invoke operation: JSON object string of parameters'),
52
+ .describe(`Space ID (format: "username/space-name"). Required for operation = ${VIEW_PARAMETERS} or invoke.`),
53
+ parameters: z.string().optional().describe('Required for invoke operation: JSON object string of parameters'),
39
54
  search_query: z
40
55
  .string()
41
56
  .optional()
@@ -49,14 +64,12 @@ export const spaceArgsSchema = z.object({
49
64
  * Zod schema for operation arguments (dynamic mode with DYNAMIC_SPACE_DATA)
50
65
  */
51
66
  export const dynamicSpaceArgsSchema = z.object({
52
- operation: z.enum(DYNAMIC_OPERATION_NAMES).optional().describe('Operation to execute.'),
67
+ operation: z.enum(DYNAMIC_OPERATION_NAMES).optional().describe('Operation to perform.'),
53
68
  space_name: z
54
69
  .string()
55
70
  .optional()
56
- .describe(
57
- 'The Hugging Face space ID (format: "username/space-name"). Required for view_parameters and invoke operations.'
58
- ),
59
- parameters: z.string().optional().describe('For invoke operation: JSON object string of parameters'),
71
+ .describe(`Space ID (format: "username/space-name"). Required for "${VIEW_PARAMETERS}" and "invoke" operations.`),
72
+ parameters: z.string().optional().describe('JSON object string of parameters. Only used for "invoke" operation.'),
60
73
  });
61
74
 
62
75
  /**
@@ -130,13 +143,6 @@ export interface JsonSchema {
130
143
  [key: string]: unknown;
131
144
  }
132
145
 
133
- /**
134
- * File input help message constant
135
- */
136
- export const FILE_INPUT_HELP_MESSAGE =
137
- 'Provide a publicly accessible URL (http:// or https://) for the Gradio file input. ' +
138
- "Content previously generated by 'invoke' is usable, as well as Hub Repository URLs. ";
139
-
140
146
  /**
141
147
  * Check if a property is a FileData type
142
148
  */
@@ -0,0 +1,204 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { SSEClientTransport, type SSEClientTransportOptions } from '@modelcontextprotocol/sdk/client/sse.js';
3
+ import { CallToolResultSchema, type ServerNotification, type ServerRequest } from '@modelcontextprotocol/sdk/types.js';
4
+ import type { RequestHandlerExtra, RequestOptions } from '@modelcontextprotocol/sdk/shared/protocol.js';
5
+
6
+ export interface GradioCallResult {
7
+ result: typeof CallToolResultSchema._type;
8
+ capturedHeaders: Record<string, string>;
9
+ }
10
+
11
+ export interface GradioCallOptions {
12
+ /** Called for every response to capture custom headers */
13
+ onHeaders?: (headers: Headers) => void;
14
+ /** Log the X-Proxied-Replica header to stderr once */
15
+ logProxiedReplica?: boolean;
16
+ }
17
+
18
+ /**
19
+ * Extract the replica ID from the X-Proxied-Replica header.
20
+ * Example: "oyerizs4-dspr4" => "dspr4"
21
+ */
22
+ export function extractReplicaId(headerValue: string | undefined): string | null {
23
+ if (!headerValue) return null;
24
+ const parts = headerValue.split('-');
25
+ if (parts.length < 2) return null;
26
+ const replicaId = parts[parts.length - 1];
27
+ if (!replicaId || replicaId.trim() === '') return null;
28
+ return replicaId;
29
+ }
30
+
31
+ /**
32
+ * Rewrites any Gradio API URLs in text content to include the replica path segment.
33
+ * Example: https://mcp-tools-qwen-image-fast.hf.space/gradio_api =>
34
+ * https://mcp-tools-qwen-image-fast.hf.space/--replicas/<replica_id>/gradio_api
35
+ */
36
+ export function rewriteReplicaUrlsInResult(
37
+ result: typeof CallToolResultSchema._type,
38
+ proxiedReplicaHeader: string | undefined
39
+ ): typeof CallToolResultSchema._type {
40
+ if (process.env.NO_REPLICA_REWRITE) return result;
41
+ const replicaId = extractReplicaId(proxiedReplicaHeader);
42
+ if (!replicaId) return result;
43
+
44
+ const urlPattern = /https:\/\/([^\s"']+)\/gradio_api(\S*)?/g;
45
+
46
+ const rewriteText = (text: string): string =>
47
+ text.replace(urlPattern, (_match, host, rest = '') => {
48
+ return `https://${host}/--replicas/${replicaId}/gradio_api${rest}`;
49
+ });
50
+
51
+ let changed = false;
52
+ const newContent = result.content.map((item) => {
53
+ if (typeof item === 'string') {
54
+ const rewritten = rewriteText(item);
55
+ if (rewritten !== item) {
56
+ changed = true;
57
+ return { type: 'text', text: rewritten } as (typeof result.content)[number];
58
+ }
59
+ return { type: 'text', text: item } as (typeof result.content)[number];
60
+ }
61
+
62
+ if (item && typeof item === 'object' && 'text' in item && typeof item.text === 'string') {
63
+ const rewritten = rewriteText(item.text);
64
+ if (rewritten !== item.text) {
65
+ changed = true;
66
+ return { ...item, text: rewritten };
67
+ }
68
+ }
69
+
70
+ return item;
71
+ });
72
+
73
+ if (!changed) return result;
74
+ return {
75
+ ...result,
76
+ content: newContent,
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Shared helper to call a Gradio MCP tool over SSE, capturing response headers (including X-Proxied-Replica).
82
+ * This handles SSE setup, optional progress relay, and cleans up the client connection.
83
+ */
84
+ export async function callGradioToolWithHeaders(
85
+ sseUrl: string,
86
+ toolName: string,
87
+ parameters: Record<string, unknown>,
88
+ hfToken: string | undefined,
89
+ extra: RequestHandlerExtra<ServerRequest, ServerNotification> | undefined,
90
+ options: GradioCallOptions = {}
91
+ ): Promise<GradioCallResult> {
92
+ const capturedHeaders: Record<string, string> = {};
93
+ let loggedHeader = false;
94
+
95
+ const handleHeaders = (headers: Headers): void => {
96
+ const proxiedReplica = headers.get('x-proxied-replica') ?? '';
97
+ if (proxiedReplica) {
98
+ capturedHeaders['x-proxied-replica'] = proxiedReplica;
99
+ }
100
+ if (options.logProxiedReplica && !loggedHeader) {
101
+ loggedHeader = true;
102
+ }
103
+ options.onHeaders?.(headers);
104
+ };
105
+
106
+ const captureHeadersFetch: SSEClientTransportOptions['fetch'] = async (url, init) => {
107
+ const response = await fetch(url, init);
108
+ handleHeaders(response.headers);
109
+ return response;
110
+ };
111
+
112
+ type EventSourceFetch = NonNullable<SSEClientTransportOptions['eventSourceInit']>['fetch'];
113
+ const buildEventSourceFetch =
114
+ (extraHeaders?: Record<string, string>): EventSourceFetch =>
115
+ (url, init) => {
116
+ const headers = new Headers(init?.headers);
117
+ if (extraHeaders) {
118
+ Object.entries(extraHeaders).forEach(([key, value]) => headers.set(key, value));
119
+ }
120
+ const requestInit: RequestInit = { ...(init as RequestInit), headers };
121
+ return captureHeadersFetch(url.toString(), requestInit);
122
+ };
123
+
124
+ // Create MCP client
125
+ const remoteClient = new Client(
126
+ {
127
+ name: 'hf-mcp-gradio-client',
128
+ version: '1.0.0',
129
+ },
130
+ {
131
+ capabilities: {},
132
+ }
133
+ );
134
+
135
+ // Create SSE transport with HF token if available
136
+ const transportOptions: SSEClientTransportOptions = {
137
+ fetch: captureHeadersFetch,
138
+ };
139
+ if (hfToken) {
140
+ const headerName = 'X-HF-Authorization';
141
+ const customHeaders = {
142
+ [headerName]: `Bearer ${hfToken}`,
143
+ };
144
+
145
+ // Headers for POST requests
146
+ transportOptions.requestInit = {
147
+ headers: customHeaders,
148
+ };
149
+
150
+ // Headers for SSE connection
151
+ transportOptions.eventSourceInit = {
152
+ fetch: buildEventSourceFetch(customHeaders),
153
+ };
154
+ } else {
155
+ transportOptions.eventSourceInit = {
156
+ fetch: buildEventSourceFetch(),
157
+ };
158
+ }
159
+
160
+ const transport = new SSEClientTransport(new URL(sseUrl), transportOptions);
161
+ await remoteClient.connect(transport);
162
+
163
+ try {
164
+ // Check if the client is requesting progress notifications
165
+ const progressToken = extra?._meta?.progressToken;
166
+ const requestOptions: RequestOptions = {};
167
+
168
+ if (progressToken !== undefined && extra) {
169
+ // Fire-and-forget; best-effort relay
170
+ requestOptions.onprogress = (progress) => {
171
+ void extra.sendNotification({
172
+ method: 'notifications/progress',
173
+ params: {
174
+ progressToken,
175
+ progress: progress.progress,
176
+ total: progress.total,
177
+ message: progress.message,
178
+ },
179
+ });
180
+ };
181
+ requestOptions.resetTimeoutOnProgress = true;
182
+ }
183
+
184
+ const result = await remoteClient.request(
185
+ {
186
+ method: 'tools/call',
187
+ params: {
188
+ name: toolName,
189
+ arguments: parameters,
190
+ _meta: progressToken !== undefined ? { progressToken } : undefined,
191
+ },
192
+ },
193
+ CallToolResultSchema,
194
+ requestOptions
195
+ );
196
+
197
+ const proxiedReplica = capturedHeaders['x-proxied-replica'];
198
+ const rewritten = rewriteReplicaUrlsInResult(result, proxiedReplica);
199
+
200
+ return { result: rewritten, capturedHeaders };
201
+ } finally {
202
+ await remoteClient.close();
203
+ }
204
+ }
@@ -0,0 +1,74 @@
1
+ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
2
+
3
+ export type ParsedSchemaFormat = 'array' | 'object';
4
+
5
+ export interface ParsedGradioSchema {
6
+ format: ParsedSchemaFormat;
7
+ tools: Array<{ name: string; description?: string; inputSchema: unknown }>;
8
+ }
9
+
10
+ /**
11
+ * Parse a Gradio MCP schema that may be returned as either:
12
+ * - Array format: [{ name, description?, inputSchema }, ...]
13
+ * - Object format: { toolName: { properties, required, description?, ... }, ... }
14
+ *
15
+ * Returns a normalized list of tools and the detected format.
16
+ * Throws if the schema is invalid or empty.
17
+ */
18
+ export function parseGradioSchemaResponse(schemaResponse: unknown): ParsedGradioSchema {
19
+ // Array format
20
+ if (Array.isArray(schemaResponse)) {
21
+ const tools = (schemaResponse as Array<unknown>).filter((tool): tool is { name: string; description?: string; inputSchema: unknown } => {
22
+ return (
23
+ typeof tool === 'object' &&
24
+ tool !== null &&
25
+ 'name' in tool &&
26
+ typeof (tool as { name?: unknown }).name === 'string' &&
27
+ 'inputSchema' in tool
28
+ );
29
+ });
30
+
31
+ if (tools.length === 0) {
32
+ throw new Error('Invalid schema: no tools found in array schema');
33
+ }
34
+
35
+ return {
36
+ format: 'array',
37
+ tools,
38
+ };
39
+ }
40
+
41
+ // Object format
42
+ if (typeof schemaResponse === 'object' && schemaResponse !== null) {
43
+ const entries = Object.entries(schemaResponse as Record<string, unknown>);
44
+ const tools: ParsedGradioSchema['tools'] = entries.map(([name, toolSchema]) => ({
45
+ name,
46
+ description: typeof (toolSchema as { description?: unknown }).description === 'string' ? (toolSchema as { description?: string }).description : undefined,
47
+ inputSchema: toolSchema,
48
+ }));
49
+
50
+ if (tools.length === 0) {
51
+ throw new Error('Invalid schema: no tools found in object schema');
52
+ }
53
+
54
+ return {
55
+ format: 'object',
56
+ tools,
57
+ };
58
+ }
59
+
60
+ throw new Error('Invalid schema format: expected array or object');
61
+ }
62
+
63
+ /**
64
+ * Convert ParsedGradioSchema.tools into SDK Tool[] shape, filtering out <lambda tools.
65
+ */
66
+ export function normalizeParsedTools(parsed: ParsedGradioSchema): Tool[] {
67
+ return parsed.tools
68
+ .filter((t) => !t.name.toLowerCase().includes('<lambda'))
69
+ .map((parsedTool) => ({
70
+ name: parsedTool.name,
71
+ description: parsedTool.description || `${parsedTool.name} tool`,
72
+ inputSchema: parsedTool.inputSchema as Tool['inputSchema'],
73
+ }));
74
+ }
@@ -1,5 +1,5 @@
1
1
  import type { SchemaComplexityResult, ParameterInfo } from '../types.js';
2
- import { FILE_INPUT_HELP_MESSAGE } from '../types.js';
2
+ import { FILE_INPUT_HELP_MESSAGE, VIEW_PARAMETERS } from '../types.js';
3
3
 
4
4
  /**
5
5
  * Formats parameter schema for display to users
@@ -194,7 +194,7 @@ export function formatValidationError(errors: string[], spaceName: string): stri
194
194
  output += `- ${error}\n`;
195
195
  }
196
196
 
197
- output += `\nUse the view_parameters operation to see all required parameters and their types.`;
197
+ output += `\nUse the ${VIEW_PARAMETERS} operation to see all required parameters and their types.`;
198
198
 
199
199
  return output;
200
200
  }
@@ -0,0 +1,77 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
+ import { rewriteReplicaUrlsInResult, extractReplicaId } from '../src/space/utils/gradio-caller.js';
3
+ import { CallToolResultSchema } from '@modelcontextprotocol/sdk/types.js';
4
+
5
+ const baseResult: typeof CallToolResultSchema._type = {
6
+ content: [],
7
+ isError: false,
8
+ };
9
+
10
+ describe('extractReplicaId', () => {
11
+ it('extracts the suffix after hyphen', () => {
12
+ expect(extractReplicaId('oyerizs4-dspr4')).toBe('dspr4');
13
+ });
14
+
15
+ it('returns null when no hyphen exists', () => {
16
+ expect(extractReplicaId('singlepart')).toBeNull();
17
+ });
18
+
19
+ it('returns null for empty input', () => {
20
+ expect(extractReplicaId('')).toBeNull();
21
+ expect(extractReplicaId(undefined)).toBeNull();
22
+ });
23
+ });
24
+
25
+ describe('rewriteReplicaUrlsInResult', () => {
26
+ const matchUrl = 'https://mcp-tools-qwen-image-fast.hf.space/gradio_api';
27
+ const rewrittenUrl = 'https://mcp-tools-qwen-image-fast.hf.space/--replicas/dspr4/gradio_api';
28
+
29
+ beforeEach(() => {
30
+ delete process.env.NO_REPLICA_REWRITE;
31
+ });
32
+
33
+ afterEach(() => {
34
+ delete process.env.NO_REPLICA_REWRITE;
35
+ });
36
+
37
+ it('rewrites URLs in text content when header is present', () => {
38
+ const result: typeof CallToolResultSchema._type = {
39
+ ...baseResult,
40
+ content: [
41
+ { type: 'text', text: `prefix ${matchUrl} suffix` },
42
+ `plain ${matchUrl}`,
43
+ { type: 'image', data: 'noop' },
44
+ ],
45
+ };
46
+
47
+ const rewritten = rewriteReplicaUrlsInResult(result, 'oyerizs4-dspr4');
48
+
49
+ expect((rewritten.content[0] as { text: string }).text).toContain(rewrittenUrl);
50
+ expect((rewritten.content[1] as { text: string }).text).toContain(rewrittenUrl);
51
+ // Non-text blocks untouched
52
+ expect(rewritten.content[2]).toEqual(result.content[2]);
53
+ });
54
+
55
+ it('does nothing when header is missing', () => {
56
+ const result: typeof CallToolResultSchema._type = {
57
+ ...baseResult,
58
+ content: [{ type: 'text', text: `prefix ${matchUrl} suffix` }],
59
+ };
60
+
61
+ const rewritten = rewriteReplicaUrlsInResult(result, undefined);
62
+
63
+ expect(rewritten).toEqual(result);
64
+ });
65
+
66
+ it('respects NO_REPLICA_REWRITE env', () => {
67
+ process.env.NO_REPLICA_REWRITE = '1';
68
+ const result: typeof CallToolResultSchema._type = {
69
+ ...baseResult,
70
+ content: [{ type: 'text', text: matchUrl }],
71
+ };
72
+
73
+ const rewritten = rewriteReplicaUrlsInResult(result, 'oyerizs4-dspr4');
74
+
75
+ expect(rewritten).toEqual(result);
76
+ });
77
+ });
@@ -1 +0,0 @@
1
- {"version":3,"file":"space-tool.d.ts","sourceRoot":"","sources":["../../src/space/space-tool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAC5F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8CAA8C,CAAC;AACxF,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAEN,KAAK,SAAS,EACd,KAAK,YAAY,EAIjB,MAAM,YAAY,CAAC;AAOpB,cAAc,YAAY,CAAC;AAmJ3B,wBAAgB,yBAAyB,IAAI;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACnC,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,OAAO,CAAC;QAAC,aAAa,EAAE,OAAO,CAAA;KAAE,CAAC;CAC9E,CAgBA;AAKD,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;CAW5B,CAAC;AAKX,qBAAa,SAAS;IACrB,OAAO,CAAC,OAAO,CAAC,CAAS;gBAEb,OAAO,CAAC,EAAE,MAAM;IAStB,OAAO,CACZ,MAAM,EAAE,SAAS,EACjB,KAAK,CAAC,EAAE,mBAAmB,CAAC,aAAa,EAAE,kBAAkB,CAAC,GAC5D,OAAO,CAAC,YAAY,GAAG,UAAU,CAAC;YAgEvB,UAAU;YAOV,cAAc;YAOd,oBAAoB;YAyBpB,YAAY;CA+C1B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"space-tool.js","sourceRoot":"","sources":["../../src/space/space-tool.ts"],"names":[],"mappings":"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"}