@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.
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/space/commands/discover.d.ts.map +1 -1
- package/dist/space/commands/discover.js +2 -1
- package/dist/space/commands/discover.js.map +1 -1
- package/dist/space/commands/dynamic-find.d.ts.map +1 -1
- package/dist/space/commands/dynamic-find.js +2 -1
- package/dist/space/commands/dynamic-find.js.map +1 -1
- package/dist/space/commands/invoke.d.ts.map +1 -1
- package/dist/space/commands/invoke.js +14 -110
- package/dist/space/commands/invoke.js.map +1 -1
- package/dist/space/{space-tool.d.ts → dynamic-space-tool.d.ts} +2 -2
- package/dist/space/dynamic-space-tool.d.ts.map +1 -0
- package/dist/space/{space-tool.js → dynamic-space-tool.js} +29 -45
- package/dist/space/dynamic-space-tool.js.map +1 -0
- package/dist/space/types.d.ts +3 -1
- package/dist/space/types.d.ts.map +1 -1
- package/dist/space/types.js +14 -9
- package/dist/space/types.js.map +1 -1
- package/dist/space/utils/gradio-caller.d.ts +14 -0
- package/dist/space/utils/gradio-caller.d.ts.map +1 -0
- package/dist/space/utils/gradio-caller.js +138 -0
- package/dist/space/utils/gradio-caller.js.map +1 -0
- package/dist/space/utils/gradio-schema.d.ts +13 -0
- package/dist/space/utils/gradio-schema.d.ts.map +1 -0
- package/dist/space/utils/gradio-schema.js +44 -0
- package/dist/space/utils/gradio-schema.js.map +1 -0
- package/dist/space/utils/parameter-formatter.js +2 -2
- package/dist/space/utils/parameter-formatter.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +3 -1
- package/src/space/commands/discover.ts +2 -1
- package/src/space/commands/dynamic-find.ts +2 -1
- package/src/space/commands/invoke.ts +13 -147
- package/src/space/{space-tool.ts → dynamic-space-tool.ts} +29 -44
- package/src/space/types.ts +24 -18
- package/src/space/utils/gradio-caller.ts +204 -0
- package/src/space/utils/gradio-schema.ts +74 -0
- package/src/space/utils/parameter-formatter.ts +2 -2
- package/test/gradio-caller.test.ts +77 -0
- package/dist/space/space-tool.d.ts.map +0 -1
- 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
|
-
###
|
|
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": "
|
|
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 \`
|
|
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
|
|
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 = `#
|
|
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
|
-
|
|
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
|
-
###
|
|
119
|
-
Display the parameter schema for
|
|
114
|
+
### ${VIEW_PARAMETERS}
|
|
115
|
+
Display the parameter schema for the Space.
|
|
120
116
|
|
|
121
117
|
**Example:**
|
|
122
118
|
\`\`\`json
|
|
123
119
|
{
|
|
124
|
-
"operation": "
|
|
120
|
+
"operation": "${VIEW_PARAMETERS}",
|
|
125
121
|
"space_name": "evalstate/FLUX1_schnell"
|
|
126
122
|
}
|
|
127
123
|
\`\`\`
|
|
128
124
|
|
|
129
125
|
### invoke
|
|
130
|
-
Execute a
|
|
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
|
-
? '
|
|
177
|
-
'Call with no
|
|
178
|
-
: 'Find (semantic/task search), inspect (view parameter schema) and dynamically invoke
|
|
179
|
-
'Call with no
|
|
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
|
|
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
|
|
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
|
|
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": "
|
|
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 "
|
|
349
|
+
Use "${VIEW_PARAMETERS}" to see what parameters this space accepts.`,
|
|
365
350
|
totalResults: 0,
|
|
366
351
|
resultsShared: 0,
|
|
367
352
|
isError: true,
|
package/src/space/types.ts
CHANGED
|
@@ -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
|
-
|
|
9
|
-
export 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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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":"AAIA,OAAO,EACN,eAAe,EAGf,kBAAkB,EAClB,iBAAiB,EACjB,kBAAkB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGnD,cAAc,YAAY,CAAC;AAK3B,MAAM,kBAAkB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwE1B,CAAC;AAMF,MAAM,0BAA0B,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmDlC,CAAC;AAKF,SAAS,oBAAoB;IAC5B,OAAO,kBAAkB,EAAE,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,kBAAkB,CAAC;AAC/E,CAAC;AAMD,MAAM,UAAU,yBAAyB;IAMxC,MAAM,WAAW,GAAG,kBAAkB,EAAE,CAAC;IACzC,OAAO;QACN,IAAI,EAAE,eAAe;QACrB,WAAW,EAAE,WAAW;YACvB,CAAC,CAAC,iLAAiL;gBAClL,qDAAqD;YACtD,CAAC,CAAC,yGAAyG;gBAC1G,qDAAqD;QACvD,MAAM,EAAE,kBAAkB,EAAE;QAC5B,WAAW,EAAE;YACZ,KAAK,EAAE,qCAAqC;YAC5C,YAAY,EAAE,KAAK;YACnB,aAAa,EAAE,IAAI;SACnB;KACD,CAAC;AACH,CAAC;AAKD,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACxC,IAAI,EAAE,eAAe;IACrB,WAAW,EACV,yGAAyG;QACzG,qDAAqD;IACtD,MAAM,EAAE,eAAe;IACvB,WAAW,EAAE;QACZ,KAAK,EAAE,qCAAqC;QAC5C,YAAY,EAAE,KAAK;QACnB,aAAa,EAAE,IAAI;KACnB;CACQ,CAAC;AAKX,MAAM,OAAO,SAAS;IACb,OAAO,CAAU;IAEzB,YAAY,OAAgB;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACxB,CAAC;IAOD,KAAK,CAAC,OAAO,CACZ,MAAiB,EACjB,KAA8D;QAE9D,MAAM,kBAAkB,GAAG,MAAM,CAAC,SAAS,CAAC;QAG5C,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACzB,OAAO;gBACN,SAAS,EAAE,oBAAoB,EAAE;gBACjC,YAAY,EAAE,CAAC;gBACf,aAAa,EAAE,CAAC;aAChB,CAAC;QACH,CAAC;QAGD,MAAM,mBAAmB,GAAG,kBAAkB,CAAC,WAAW,EAAE,CAAC;QAC7D,MAAM,eAAe,GAAG,iBAAiB,EAAE,CAAC;QAC5C,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;YACpD,OAAO;gBACN,SAAS,EAAE,uBAAuB,kBAAkB;wBAChC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC;;8DAEY;gBAC1D,YAAY,EAAE,CAAC;gBACf,aAAa,EAAE,CAAC;gBAChB,OAAO,EAAE,IAAI;aACb,CAAC;QACH,CAAC;QAGD,IAAI,CAAC;YACJ,QAAQ,mBAAmB,EAAE,CAAC;gBAC7B,KAAK,MAAM;oBACV,OAAO,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;gBAEtC,KAAK,UAAU;oBACd,OAAO,MAAM,IAAI,CAAC,cAAc,EAAE,CAAC;gBAEpC,KAAK,iBAAiB;oBACrB,OAAO,MAAM,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;gBAEhD,KAAK,QAAQ;oBACZ,OAAO,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;gBAE/C;oBACC,OAAO;wBACN,SAAS,EAAE,uBAAuB,kBAAkB,GAAG;wBACvD,YAAY,EAAE,CAAC;wBACf,aAAa,EAAE,CAAC;wBAChB,OAAO,EAAE,IAAI;qBACb,CAAC;YACJ,CAAC;QACF,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5E,OAAO;gBACN,SAAS,EAAE,mBAAmB,kBAAkB,KAAK,YAAY,EAAE;gBACnE,YAAY,EAAE,CAAC;gBACf,aAAa,EAAE,CAAC;gBAChB,OAAO,EAAE,IAAI;aACb,CAAC;QACH,CAAC;IACF,CAAC;IAKO,KAAK,CAAC,UAAU,CAAC,MAAiB;QACzC,OAAO,MAAM,UAAU,CAAC,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC1E,CAAC;IAKO,KAAK,CAAC,cAAc;QAC3B,OAAO,MAAM,cAAc,EAAE,CAAC;IAC/B,CAAC;IAKO,KAAK,CAAC,oBAAoB,CAAC,MAAiB;QACnD,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACxB,OAAO;gBACN,SAAS,EAAE;;;;;;;;OAQR;gBACH,YAAY,EAAE,CAAC;gBACf,aAAa,EAAE,CAAC;gBAChB,OAAO,EAAE,IAAI;aACb,CAAC;QACH,CAAC;QAED,OAAO,MAAM,cAAc,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9D,CAAC;IAMO,KAAK,CAAC,YAAY,CACzB,MAAiB,EACjB,KAA8D;QAG9D,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACxB,OAAO;gBACN,SAAS,EAAE;;;;;;;;;OASR;gBACH,YAAY,EAAE,CAAC;gBACf,aAAa,EAAE,CAAC;gBAChB,OAAO,EAAE,IAAI;aACb,CAAC;QACH,CAAC;QAED,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACxB,OAAO;gBACN,SAAS,EAAE;;;;;;;;mBAQI,MAAM,CAAC,UAAU;;;;;iEAK6B;gBAC7D,YAAY,EAAE,CAAC;gBACf,aAAa,EAAE,CAAC;gBAChB,OAAO,EAAE,IAAI;aACb,CAAC;QACH,CAAC;QAED,OAAO,MAAM,WAAW,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IACrF,CAAC;CACD"}
|