bpHigh commited on
Commit
d16322a
·
1 Parent(s): f063810

Revamp stuff

Browse files
Files changed (2) hide show
  1. app.py +12 -10
  2. utils/huggingface_mcp_llamaindex.py +139 -44
app.py CHANGED
@@ -7,7 +7,7 @@ from utils.google_genai_llm import get_response, generate_with_gemini
7
  from utils.utils import parse_json_codefences
8
  from prompts.requirements_gathering import requirements_gathering_system_prompt
9
  from prompts.planning import hf_query_gen_prompt, hf_context_gen_prompt
10
- from utils.huggingface_mcp_llamaindex import get_hf_tools, call_hf_tool, SimpleHFMCPClient
11
  from prompts.devstral_coding_prompt import devstral_code_gen_sys_prompt, devstral_code_gen_user_prompt
12
  from dotenv import load_dotenv
13
  import os
@@ -49,7 +49,7 @@ MODAL_API_URL = os.getenv("MODAL_API_URL")
49
  BEARER_TOKEN = os.getenv("BEARER_TOKEN")
50
  CODING_MODEL = os.getenv("CODING_MODEL")
51
 
52
- HF_TOKEN = os.getenv("HF_TOKEN")
53
  def get_file_hash(file_path):
54
  """Generate a hash of the file for caching purposes"""
55
  try:
@@ -245,17 +245,19 @@ async def generate_plan(history, file_cache):
245
  if ai_msg:
246
  conversation_history += f"Assistant: {ai_msg}\n"
247
 
248
- simple_client = SimpleHFMCPClient(HF_TOKEN)
249
- connection_ok = await simple_client.test_connection()
 
250
 
251
- if not connection_ok:
252
- print("Basic connection test failed")
253
  return
254
 
255
- print("Basic connection test passed")
256
-
 
257
  # try:
258
- hf_query_gen_tool_details = await get_hf_tools(hf_token=HF_TOKEN)
259
  # except Exception as e:
260
  # hf_query_gen_tool_details = """meta=None nextCursor=None tools=[Tool(name='hf_whoami', description="Hugging Face tools are being used by authenticated user 'bpHigh'", inputSchema={'type': 'object', 'properties': {}, 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='Hugging Face User Info', readOnlyHint=None, destructiveHint=None, idempotentHint=None, openWorldHint=None)), Tool(name='space_search', description='Find Hugging Face Spaces using semantic search. Include links to the Space when presenting the results.', inputSchema={'type': 'object', 'properties': {'query': {'type': 'string', 'minLength': 1, 'maxLength': 50, 'description': 'Semantic Search Query'}, 'limit': {'type': 'number', 'default': 10, 'description': 'Number of results to return'}, 'mcp': {'type': 'boolean', 'default': False, 'description': 'Only return MCP Server enabled Spaces'}}, 'required': ['query'], 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='Hugging Face Space Search', readOnlyHint=True, destructiveHint=False, idempotentHint=None, openWorldHint=True)), Tool(name='model_search', description='Find Machine Learning models hosted on Hugging Face. Returns comprehensive information about matching models including downloads, likes, tags, and direct links. Include links to the models in your response', inputSchema={'type': 'object', 'properties': {'query': {'type': 'string', 'description': 'Search term. Leave blank and specify "sort" and "limit" to get e.g. "Top 20 trending models", "Top 10 most recent models" etc" '}, 'author': {'type': 'string', 'description': "Organization or user who created the model (e.g., 'google', 'meta-llama', 'microsoft')"}, 'task': {'type': 'string', 'description': "Model task type (e.g., 'text-generation', 'image-classification', 'translation')"}, 'library': {'type': 'string', 'description': "Framework the model uses (e.g., 'transformers', 'diffusers', 'timm')"}, 'sort': {'type': 'string', 'enum': ['trendingScore', 'downloads', 'likes', 'createdAt', 'lastModified'], 'description': 'Sort order: trendingScore, downloads , likes, createdAt, lastModified'}, 'limit': {'type': 'number', 'minimum': 1, 'maximum': 100, 'default': 20, 'description': 'Maximum number of results to return'}}, 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='Model Search', readOnlyHint=True, destructiveHint=False, idempotentHint=None, openWorldHint=True)), Tool(name='model_details', description='Get detailed information about a specific model from the Hugging Face Hub.', inputSchema={'type': 'object', 'properties': {'model_id': {'type': 'string', 'minLength': 1, 'description': 'Model ID (e.g., microsoft/DialoGPT-large)'}}, 'required': ['model_id'], 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='Model Details', readOnlyHint=True, destructiveHint=False, idempotentHint=None, openWorldHint=False)), Tool(name='paper_search', description="Find Machine Learning research papers on the Hugging Face hub. Include 'Link to paper' When presenting the results. Consider whether tabulating results matches user intent.", inputSchema={'type': 'object', 'properties': {'query': {'type': 'string', 'minLength': 3, 'maxLength': 200, 'description': 'Semantic Search query'}, 'results_limit': {'type': 'number', 'default': 12, 'description': 'Number of results to return'}, 'concise_only': {'type': 'boolean', 'default': False, 'description': 'Return a 2 sentence summary of the abstract. Use for broad search terms which may return a lot of results. Check with User if unsure.'}}, 'required': ['query'], 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='Paper Search', readOnlyHint=True, destructiveHint=False, idempotentHint=None, openWorldHint=True)), Tool(name='dataset_search', description='Find Datasets hosted on the Hugging Face hub. Returns comprehensive information about matching datasets including downloads, likes, tags, and direct links. Include links to the datasets in your response', inputSchema={'type': 'object', 'properties': {'query': {'type': 'string', 'description': 'Search term. Leave blank and specify "sort" and "limit" to get e.g. "Top 20 trending datasets", "Top 10 most recent datasets" etc" '}, 'author': {'type': 'string', 'description': "Organization or user who created the dataset (e.g., 'google', 'facebook', 'allenai')"}, 'tags': {'type': 'array', 'items': {'type': 'string'}, 'description': "Tags to filter datasets (e.g., ['language:en', 'size_categories:1M<n<10M', 'task_categories:text-classification'])"}, 'sort': {'type': 'string', 'enum': ['trendingScore', 'downloads', 'likes', 'createdAt', 'lastModified'], 'description': 'Sort order: trendingScore, downloads, likes, createdAt, lastModified'}, 'limit': {'type': 'number', 'minimum': 1, 'maximum': 100, 'default': 20, 'description': 'Maximum number of results to return'}}, 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='Dataset Search', readOnlyHint=True, destructiveHint=False, idempotentHint=None, openWorldHint=True)), Tool(name='dataset_details', description='Get detailed information about a specific dataset on Hugging Face Hub.', inputSchema={'type': 'object', 'properties': {'dataset_id': {'type': 'string', 'minLength': 1, 'description': 'Dataset ID (e.g., squad, glue, imdb)'}}, 'required': ['dataset_id'], 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='Dataset Details', readOnlyHint=True, destructiveHint=False, idempotentHint=None, openWorldHint=False)), Tool(name='gr1_evalstate_flux1_schnell', description='Generate an image using the Flux 1 Schnell Image Generator. (from evalstate/flux1_schnell)', inputSchema={'type': 'object', 'properties': {'prompt': {'type': 'string'}, 'seed': {'type': 'number', 'description': 'numeric value between 0 and 2147483647'}, 'randomize_seed': {'type': 'boolean', 'default': True}, 'width': {'type': 'number', 'description': 'numeric value between 256 and 2048', 'default': 1024}, 'height': {'type': 'number', 'description': 'numeric value between 256 and 2048', 'default': 1024}, 'num_inference_steps': {'type': 'number', 'description': 'numeric value between 1 and 50', 'default': 4}}, 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='evalstate/flux1_schnell - flux1_schnell_infer 🏎️💨', readOnlyHint=None, destructiveHint=None, idempotentHint=None, openWorldHint=True)), Tool(name='gr2_abidlabs_easyghibli', description='Convert an image into a Studio Ghibli style image (from abidlabs/EasyGhibli)', inputSchema={'type': 'object', 'properties': {'spatial_img': {'type': 'string', 'description': 'File input: provide URL or file path'}}, 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='abidlabs/EasyGhibli - abidlabs_EasyGhiblisingle_condition_generate_image 🦀', readOnlyHint=None, destructiveHint=None, idempotentHint=None, openWorldHint=True)), Tool(name='gr3_linoyts_framepack_f1', description='FramePack_F1_end_process tool from linoyts/FramePack-F1', inputSchema={'type': 'object', 'properties': {}, 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='linoyts/FramePack-F1 - FramePack_F1_end_process 📹⚡️', readOnlyHint=None, destructiveHint=None, idempotentHint=None, openWorldHint=True))]"""
261
  # print(str(e))
@@ -271,7 +273,7 @@ async def generate_plan(history, file_cache):
271
 
272
  # Call tool to get tool calls
273
  try:
274
- tool_calls = await asyncio.gather(*[call_hf_tool(HF_TOKEN, step['tool'], step['args']) for step in parsed_plan])
275
  except Exception as e:
276
  tool_calls = []
277
  print(tool_calls)
 
7
  from utils.utils import parse_json_codefences
8
  from prompts.requirements_gathering import requirements_gathering_system_prompt
9
  from prompts.planning import hf_query_gen_prompt, hf_context_gen_prompt
10
+ from utils.huggingface_mcp_llamaindex import get_hf_tools, call_hf_tool, diagnose_connection
11
  from prompts.devstral_coding_prompt import devstral_code_gen_sys_prompt, devstral_code_gen_user_prompt
12
  from dotenv import load_dotenv
13
  import os
 
49
  BEARER_TOKEN = os.getenv("BEARER_TOKEN")
50
  CODING_MODEL = os.getenv("CODING_MODEL")
51
 
52
+ MCP_TOKEN = os.getenv("MCP_TOKEN")
53
  def get_file_hash(file_path):
54
  """Generate a hash of the file for caching purposes"""
55
  try:
 
245
  if ai_msg:
246
  conversation_history += f"Assistant: {ai_msg}\n"
247
 
248
+ print("Running connection diagnostics...")
249
+ diagnostics = await diagnose_connection(MCP_TOKEN)
250
+ print(f"Diagnostics: {json.dumps(diagnostics, indent=2)}")
251
 
252
+ if not diagnostics["connection_test"]:
253
+ print("Basic connection failed - check token and network")
254
  return
255
 
256
+ if not diagnostics["tools_test"]:
257
+ print("Tools retrieval failed - check server status")
258
+ return
259
  # try:
260
+ hf_query_gen_tool_details = await get_hf_tools(hf_token=MCP_TOKEN)
261
  # except Exception as e:
262
  # hf_query_gen_tool_details = """meta=None nextCursor=None tools=[Tool(name='hf_whoami', description="Hugging Face tools are being used by authenticated user 'bpHigh'", inputSchema={'type': 'object', 'properties': {}, 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='Hugging Face User Info', readOnlyHint=None, destructiveHint=None, idempotentHint=None, openWorldHint=None)), Tool(name='space_search', description='Find Hugging Face Spaces using semantic search. Include links to the Space when presenting the results.', inputSchema={'type': 'object', 'properties': {'query': {'type': 'string', 'minLength': 1, 'maxLength': 50, 'description': 'Semantic Search Query'}, 'limit': {'type': 'number', 'default': 10, 'description': 'Number of results to return'}, 'mcp': {'type': 'boolean', 'default': False, 'description': 'Only return MCP Server enabled Spaces'}}, 'required': ['query'], 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='Hugging Face Space Search', readOnlyHint=True, destructiveHint=False, idempotentHint=None, openWorldHint=True)), Tool(name='model_search', description='Find Machine Learning models hosted on Hugging Face. Returns comprehensive information about matching models including downloads, likes, tags, and direct links. Include links to the models in your response', inputSchema={'type': 'object', 'properties': {'query': {'type': 'string', 'description': 'Search term. Leave blank and specify "sort" and "limit" to get e.g. "Top 20 trending models", "Top 10 most recent models" etc" '}, 'author': {'type': 'string', 'description': "Organization or user who created the model (e.g., 'google', 'meta-llama', 'microsoft')"}, 'task': {'type': 'string', 'description': "Model task type (e.g., 'text-generation', 'image-classification', 'translation')"}, 'library': {'type': 'string', 'description': "Framework the model uses (e.g., 'transformers', 'diffusers', 'timm')"}, 'sort': {'type': 'string', 'enum': ['trendingScore', 'downloads', 'likes', 'createdAt', 'lastModified'], 'description': 'Sort order: trendingScore, downloads , likes, createdAt, lastModified'}, 'limit': {'type': 'number', 'minimum': 1, 'maximum': 100, 'default': 20, 'description': 'Maximum number of results to return'}}, 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='Model Search', readOnlyHint=True, destructiveHint=False, idempotentHint=None, openWorldHint=True)), Tool(name='model_details', description='Get detailed information about a specific model from the Hugging Face Hub.', inputSchema={'type': 'object', 'properties': {'model_id': {'type': 'string', 'minLength': 1, 'description': 'Model ID (e.g., microsoft/DialoGPT-large)'}}, 'required': ['model_id'], 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='Model Details', readOnlyHint=True, destructiveHint=False, idempotentHint=None, openWorldHint=False)), Tool(name='paper_search', description="Find Machine Learning research papers on the Hugging Face hub. Include 'Link to paper' When presenting the results. Consider whether tabulating results matches user intent.", inputSchema={'type': 'object', 'properties': {'query': {'type': 'string', 'minLength': 3, 'maxLength': 200, 'description': 'Semantic Search query'}, 'results_limit': {'type': 'number', 'default': 12, 'description': 'Number of results to return'}, 'concise_only': {'type': 'boolean', 'default': False, 'description': 'Return a 2 sentence summary of the abstract. Use for broad search terms which may return a lot of results. Check with User if unsure.'}}, 'required': ['query'], 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='Paper Search', readOnlyHint=True, destructiveHint=False, idempotentHint=None, openWorldHint=True)), Tool(name='dataset_search', description='Find Datasets hosted on the Hugging Face hub. Returns comprehensive information about matching datasets including downloads, likes, tags, and direct links. Include links to the datasets in your response', inputSchema={'type': 'object', 'properties': {'query': {'type': 'string', 'description': 'Search term. Leave blank and specify "sort" and "limit" to get e.g. "Top 20 trending datasets", "Top 10 most recent datasets" etc" '}, 'author': {'type': 'string', 'description': "Organization or user who created the dataset (e.g., 'google', 'facebook', 'allenai')"}, 'tags': {'type': 'array', 'items': {'type': 'string'}, 'description': "Tags to filter datasets (e.g., ['language:en', 'size_categories:1M<n<10M', 'task_categories:text-classification'])"}, 'sort': {'type': 'string', 'enum': ['trendingScore', 'downloads', 'likes', 'createdAt', 'lastModified'], 'description': 'Sort order: trendingScore, downloads, likes, createdAt, lastModified'}, 'limit': {'type': 'number', 'minimum': 1, 'maximum': 100, 'default': 20, 'description': 'Maximum number of results to return'}}, 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='Dataset Search', readOnlyHint=True, destructiveHint=False, idempotentHint=None, openWorldHint=True)), Tool(name='dataset_details', description='Get detailed information about a specific dataset on Hugging Face Hub.', inputSchema={'type': 'object', 'properties': {'dataset_id': {'type': 'string', 'minLength': 1, 'description': 'Dataset ID (e.g., squad, glue, imdb)'}}, 'required': ['dataset_id'], 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='Dataset Details', readOnlyHint=True, destructiveHint=False, idempotentHint=None, openWorldHint=False)), Tool(name='gr1_evalstate_flux1_schnell', description='Generate an image using the Flux 1 Schnell Image Generator. (from evalstate/flux1_schnell)', inputSchema={'type': 'object', 'properties': {'prompt': {'type': 'string'}, 'seed': {'type': 'number', 'description': 'numeric value between 0 and 2147483647'}, 'randomize_seed': {'type': 'boolean', 'default': True}, 'width': {'type': 'number', 'description': 'numeric value between 256 and 2048', 'default': 1024}, 'height': {'type': 'number', 'description': 'numeric value between 256 and 2048', 'default': 1024}, 'num_inference_steps': {'type': 'number', 'description': 'numeric value between 1 and 50', 'default': 4}}, 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='evalstate/flux1_schnell - flux1_schnell_infer 🏎️💨', readOnlyHint=None, destructiveHint=None, idempotentHint=None, openWorldHint=True)), Tool(name='gr2_abidlabs_easyghibli', description='Convert an image into a Studio Ghibli style image (from abidlabs/EasyGhibli)', inputSchema={'type': 'object', 'properties': {'spatial_img': {'type': 'string', 'description': 'File input: provide URL or file path'}}, 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='abidlabs/EasyGhibli - abidlabs_EasyGhiblisingle_condition_generate_image 🦀', readOnlyHint=None, destructiveHint=None, idempotentHint=None, openWorldHint=True)), Tool(name='gr3_linoyts_framepack_f1', description='FramePack_F1_end_process tool from linoyts/FramePack-F1', inputSchema={'type': 'object', 'properties': {}, 'additionalProperties': False, '$schema': 'http://json-schema.org/draft-07/schema#'}, annotations=ToolAnnotations(title='linoyts/FramePack-F1 - FramePack_F1_end_process 📹⚡️', readOnlyHint=None, destructiveHint=None, idempotentHint=None, openWorldHint=True))]"""
263
  # print(str(e))
 
273
 
274
  # Call tool to get tool calls
275
  try:
276
+ tool_calls = await asyncio.gather(*[call_hf_tool(MCP_TOKEN, step['tool'], step['args']) for step in parsed_plan])
277
  except Exception as e:
278
  tool_calls = []
279
  print(tool_calls)
utils/huggingface_mcp_llamaindex.py CHANGED
@@ -21,18 +21,22 @@ logger = logging.getLogger(__name__)
21
  class HuggingFaceMCPClient:
22
  """Client for interacting with Hugging Face MCP endpoint."""
23
 
24
- def __init__(self, hf_token: str, timeout: int = 30):
25
  """
26
  Initialize the Hugging Face MCP client.
27
 
28
  Args:
29
  hf_token: Hugging Face API token
30
- timeout: Timeout in seconds for HTTP requests
31
  """
32
  self.hf_token = hf_token
33
  self.url = "https://huggingface.co/mcp"
34
- self.headers = {"Authorization": f"Bearer {hf_token}"}
 
 
 
35
  self.timeout = timedelta(seconds=timeout)
 
36
  self.request_id_counter = 0
37
 
38
  def _get_next_request_id(self) -> int:
@@ -43,18 +47,35 @@ class HuggingFaceMCPClient:
43
  async def _send_request_and_get_response(
44
  self,
45
  method: str,
46
- params: Optional[Dict[str, Any]] = None
 
47
  ) -> Any:
48
  """
49
- Send a JSON-RPC request and wait for the response.
50
 
51
  Args:
52
  method: The JSON-RPC method name
53
  params: Optional parameters for the method
 
54
 
55
  Returns:
56
  The response result or raises an exception
57
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  request_id = self._get_next_request_id()
59
 
60
  # Create JSON-RPC request
@@ -72,11 +93,12 @@ class HuggingFaceMCPClient:
72
  url=self.url,
73
  headers=self.headers,
74
  timeout=self.timeout,
 
75
  terminate_on_close=True
76
  ) as (read_stream, write_stream, get_session_id):
77
 
78
  try:
79
- # Send initialization request first
80
  init_request = JSONRPCRequest(
81
  jsonrpc="2.0",
82
  id=self._get_next_request_id(),
@@ -84,7 +106,9 @@ class HuggingFaceMCPClient:
84
  params={
85
  "protocolVersion": "2024-11-05",
86
  "capabilities": {
87
- "tools": {}
 
 
88
  },
89
  "clientInfo": {
90
  "name": "hf-mcp-client",
@@ -96,19 +120,25 @@ class HuggingFaceMCPClient:
96
  init_message = JSONRPCMessage(init_request)
97
  init_session_message = SessionMessage(init_message)
98
 
 
99
  await write_stream.send(init_session_message)
100
 
101
- # Wait for initialization response
102
  init_response_received = False
103
  timeout_counter = 0
104
- max_iterations = 100 # Prevent infinite loops
105
 
106
  while not init_response_received and timeout_counter < max_iterations:
107
  try:
108
- response = await read_stream.receive()
 
 
 
 
109
  timeout_counter += 1
110
 
111
  if isinstance(response, Exception):
 
112
  raise response
113
 
114
  if isinstance(response, SessionMessage):
@@ -116,16 +146,30 @@ class HuggingFaceMCPClient:
116
  if isinstance(msg, JSONRPCResponse) and msg.id == init_request.id:
117
  logger.info("MCP client initialized successfully")
118
  init_response_received = True
 
 
 
 
119
  elif isinstance(msg, JSONRPCError) and msg.id == init_request.id:
120
- raise Exception(f"Initialization failed: {msg.error}")
 
 
 
 
 
 
 
 
 
121
  except Exception as e:
122
- if "ClosedResourceError" in str(type(e)):
123
  logger.error("Stream closed during initialization")
124
  raise Exception("Connection closed during initialization")
 
125
  raise
126
 
127
  if not init_response_received:
128
- raise Exception("Initialization timeout")
129
 
130
  # Send initialized notification
131
  initialized_notification = JSONRPCNotification(
@@ -136,12 +180,14 @@ class HuggingFaceMCPClient:
136
  init_notif_message = JSONRPCMessage(initialized_notification)
137
  init_notif_session_message = SessionMessage(init_notif_message)
138
 
 
139
  await write_stream.send(init_notif_session_message)
140
 
141
- # Small delay to let the notification process
142
- await asyncio.sleep(0.1)
143
 
144
  # Now send our actual request
 
145
  await write_stream.send(session_message)
146
 
147
  # Wait for the response to our request
@@ -150,26 +196,41 @@ class HuggingFaceMCPClient:
150
 
151
  while not response_received and timeout_counter < max_iterations:
152
  try:
153
- response = await read_stream.receive()
 
 
 
154
  timeout_counter += 1
155
 
156
  if isinstance(response, Exception):
 
157
  raise response
158
 
159
  if isinstance(response, SessionMessage):
160
  msg = response.message.root
161
  if isinstance(msg, JSONRPCResponse) and msg.id == request_id:
 
162
  return msg.result
163
  elif isinstance(msg, JSONRPCError) and msg.id == request_id:
164
- raise Exception(f"Request failed: {msg.error}")
 
 
 
 
 
 
 
 
 
165
  except Exception as e:
166
- if "ClosedResourceError" in str(type(e)):
167
  logger.error("Stream closed during request processing")
168
  raise Exception("Connection closed during request processing")
 
169
  raise
170
 
171
  if not response_received:
172
- raise Exception("Request timeout")
173
 
174
  except Exception as e:
175
  logger.error(f"Error during MCP communication: {e}")
@@ -178,8 +239,8 @@ class HuggingFaceMCPClient:
178
  # Ensure streams are properly closed
179
  try:
180
  await write_stream.aclose()
181
- except:
182
- pass
183
 
184
  async def get_all_tools(self) -> List[Dict[str, Any]]:
185
  """
@@ -243,7 +304,7 @@ async def get_hf_tools(hf_token: str) -> List[Dict[str, Any]]:
243
  Returns:
244
  List of tool definitions
245
  """
246
- client = HuggingFaceMCPClient(hf_token)
247
  return await client.get_all_tools()
248
 
249
 
@@ -259,31 +320,65 @@ async def call_hf_tool(hf_token: str, tool_name: str, args: Dict[str, Any]) -> A
259
  Returns:
260
  The tool's response
261
  """
262
- client = HuggingFaceMCPClient(hf_token)
263
  return await client.call_tool(tool_name, args)
264
 
265
 
266
- # Alternative simpler implementation for debugging
267
- class SimpleHFMCPClient:
268
- """Simplified version for debugging connection issues."""
 
269
 
270
- def __init__(self, hf_token: str):
271
- self.hf_token = hf_token
272
- self.url = "https://huggingface.co/mcp"
273
- self.headers = {"Authorization": f"Bearer {hf_token}"}
274
 
275
- async def test_connection(self):
276
- """Test basic connection to HF MCP endpoint."""
277
- try:
278
- async with streamablehttp_client(
279
- url=self.url,
280
- headers=self.headers,
281
- timeout=timedelta(seconds=10),
282
- terminate_on_close=True
283
- ) as (read_stream, write_stream, get_session_id):
284
- logger.info("Connection established successfully")
285
- return True
286
- except Exception as e:
287
- logger.error(f"Connection test failed: {e}")
288
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
 
 
21
  class HuggingFaceMCPClient:
22
  """Client for interacting with Hugging Face MCP endpoint."""
23
 
24
+ def __init__(self, hf_token: str, timeout: int = 60):
25
  """
26
  Initialize the Hugging Face MCP client.
27
 
28
  Args:
29
  hf_token: Hugging Face API token
30
+ timeout: Timeout in seconds for HTTP requests (increased for Spaces)
31
  """
32
  self.hf_token = hf_token
33
  self.url = "https://huggingface.co/mcp"
34
+ self.headers = {
35
+ "Authorization": f"Bearer {hf_token}",
36
+ "User-Agent": "hf-mcp-client/1.0.0" # Add user agent
37
+ }
38
  self.timeout = timedelta(seconds=timeout)
39
+ self.sse_read_timeout = timedelta(seconds=timeout * 2) # Longer SSE timeout
40
  self.request_id_counter = 0
41
 
42
  def _get_next_request_id(self) -> int:
 
47
  async def _send_request_and_get_response(
48
  self,
49
  method: str,
50
+ params: Optional[Dict[str, Any]] = None,
51
+ max_retries: int = 3
52
  ) -> Any:
53
  """
54
+ Send a JSON-RPC request and wait for the response with retry logic.
55
 
56
  Args:
57
  method: The JSON-RPC method name
58
  params: Optional parameters for the method
59
+ max_retries: Maximum number of retry attempts
60
 
61
  Returns:
62
  The response result or raises an exception
63
  """
64
+ last_exception = None
65
+
66
+ for attempt in range(max_retries):
67
+ try:
68
+ return await self._attempt_request(method, params)
69
+ except Exception as e:
70
+ last_exception = e
71
+ logger.warning(f"Attempt {attempt + 1} failed: {e}")
72
+ if attempt < max_retries - 1:
73
+ await asyncio.sleep(2 ** attempt) # Exponential backoff
74
+
75
+ raise last_exception
76
+
77
+ async def _attempt_request(self, method: str, params: Optional[Dict[str, Any]]) -> Any:
78
+ """Single attempt to send request."""
79
  request_id = self._get_next_request_id()
80
 
81
  # Create JSON-RPC request
 
93
  url=self.url,
94
  headers=self.headers,
95
  timeout=self.timeout,
96
+ sse_read_timeout=self.sse_read_timeout,
97
  terminate_on_close=True
98
  ) as (read_stream, write_stream, get_session_id):
99
 
100
  try:
101
+ # Send initialization request first with proper client info
102
  init_request = JSONRPCRequest(
103
  jsonrpc="2.0",
104
  id=self._get_next_request_id(),
 
106
  params={
107
  "protocolVersion": "2024-11-05",
108
  "capabilities": {
109
+ "tools": {},
110
+ "resources": {},
111
+ "prompts": {}
112
  },
113
  "clientInfo": {
114
  "name": "hf-mcp-client",
 
120
  init_message = JSONRPCMessage(init_request)
121
  init_session_message = SessionMessage(init_message)
122
 
123
+ logger.info("Sending initialization request...")
124
  await write_stream.send(init_session_message)
125
 
126
+ # Wait for initialization response with better error handling
127
  init_response_received = False
128
  timeout_counter = 0
129
+ max_iterations = 150 # Increased for Spaces environment
130
 
131
  while not init_response_received and timeout_counter < max_iterations:
132
  try:
133
+ # Use asyncio.wait_for to add timeout per receive operation
134
+ response = await asyncio.wait_for(
135
+ read_stream.receive(),
136
+ timeout=30.0 # 30 second timeout per receive
137
+ )
138
  timeout_counter += 1
139
 
140
  if isinstance(response, Exception):
141
+ logger.error(f"Received exception during init: {response}")
142
  raise response
143
 
144
  if isinstance(response, SessionMessage):
 
146
  if isinstance(msg, JSONRPCResponse) and msg.id == init_request.id:
147
  logger.info("MCP client initialized successfully")
148
  init_response_received = True
149
+ # Log the session ID if available
150
+ session_id = get_session_id()
151
+ if session_id:
152
+ logger.info(f"Session ID: {session_id}")
153
  elif isinstance(msg, JSONRPCError) and msg.id == init_request.id:
154
+ error_msg = f"Initialization failed: {msg.error}"
155
+ logger.error(error_msg)
156
+ raise Exception(error_msg)
157
+ else:
158
+ logger.debug(f"Received other message during init: {type(msg)}")
159
+
160
+ except asyncio.TimeoutError:
161
+ logger.warning(f"Timeout waiting for response (attempt {timeout_counter})")
162
+ if timeout_counter > 10: # After 10 timeouts, give up
163
+ raise Exception("Initialization timeout: no response from server")
164
  except Exception as e:
165
+ if "ClosedResourceError" in str(type(e)) or "StreamClosed" in str(e):
166
  logger.error("Stream closed during initialization")
167
  raise Exception("Connection closed during initialization")
168
+ logger.error(f"Error during initialization: {e}")
169
  raise
170
 
171
  if not init_response_received:
172
+ raise Exception("Initialization timeout: maximum iterations reached")
173
 
174
  # Send initialized notification
175
  initialized_notification = JSONRPCNotification(
 
180
  init_notif_message = JSONRPCMessage(initialized_notification)
181
  init_notif_session_message = SessionMessage(init_notif_message)
182
 
183
+ logger.info("Sending initialized notification...")
184
  await write_stream.send(init_notif_session_message)
185
 
186
+ # Longer delay for Spaces environment
187
+ await asyncio.sleep(1.0)
188
 
189
  # Now send our actual request
190
+ logger.info(f"Sending actual request: {method}")
191
  await write_stream.send(session_message)
192
 
193
  # Wait for the response to our request
 
196
 
197
  while not response_received and timeout_counter < max_iterations:
198
  try:
199
+ response = await asyncio.wait_for(
200
+ read_stream.receive(),
201
+ timeout=30.0
202
+ )
203
  timeout_counter += 1
204
 
205
  if isinstance(response, Exception):
206
+ logger.error(f"Received exception during request: {response}")
207
  raise response
208
 
209
  if isinstance(response, SessionMessage):
210
  msg = response.message.root
211
  if isinstance(msg, JSONRPCResponse) and msg.id == request_id:
212
+ logger.info(f"Request '{method}' completed successfully")
213
  return msg.result
214
  elif isinstance(msg, JSONRPCError) and msg.id == request_id:
215
+ error_msg = f"Request failed: {msg.error}"
216
+ logger.error(error_msg)
217
+ raise Exception(error_msg)
218
+ else:
219
+ logger.debug(f"Received other message during request: {type(msg)}")
220
+
221
+ except asyncio.TimeoutError:
222
+ logger.warning(f"Timeout waiting for request response (attempt {timeout_counter})")
223
+ if timeout_counter > 10:
224
+ raise Exception("Request timeout: no response from server")
225
  except Exception as e:
226
+ if "ClosedResourceError" in str(type(e)) or "StreamClosed" in str(e):
227
  logger.error("Stream closed during request processing")
228
  raise Exception("Connection closed during request processing")
229
+ logger.error(f"Error during request processing: {e}")
230
  raise
231
 
232
  if not response_received:
233
+ raise Exception("Request timeout: maximum iterations reached")
234
 
235
  except Exception as e:
236
  logger.error(f"Error during MCP communication: {e}")
 
239
  # Ensure streams are properly closed
240
  try:
241
  await write_stream.aclose()
242
+ except Exception as close_error:
243
+ logger.debug(f"Error closing write stream: {close_error}")
244
 
245
  async def get_all_tools(self) -> List[Dict[str, Any]]:
246
  """
 
304
  Returns:
305
  List of tool definitions
306
  """
307
+ client = HuggingFaceMCPClient(hf_token, timeout=90) # Longer timeout for Spaces
308
  return await client.get_all_tools()
309
 
310
 
 
320
  Returns:
321
  The tool's response
322
  """
323
+ client = HuggingFaceMCPClient(hf_token, timeout=90)
324
  return await client.call_tool(tool_name, args)
325
 
326
 
327
+ # Diagnostic function for Spaces environment
328
+ async def diagnose_connection(hf_token: str) -> Dict[str, Any]:
329
+ """
330
+ Diagnose connection issues with detailed logging.
331
 
332
+ Args:
333
+ hf_token: Hugging Face API token
 
 
334
 
335
+ Returns:
336
+ Diagnostic information
337
+ """
338
+ diagnostics = {
339
+ "environment": "huggingface_spaces" if os.getenv("SPACE_ID") else "local",
340
+ "space_id": os.getenv("SPACE_ID"),
341
+ "token_length": len(hf_token) if hf_token else 0,
342
+ "has_token": bool(hf_token),
343
+ "connection_test": False,
344
+ "initialization_test": False,
345
+ "tools_test": False,
346
+ "error": None
347
+ }
348
+
349
+ try:
350
+ # Test basic connection
351
+ from mcp.client.streamable_http import streamablehttp_client
352
+
353
+ headers = {
354
+ "Authorization": f"Bearer {hf_token}",
355
+ "User-Agent": "hf-mcp-diagnostic/1.0.0"
356
+ }
357
+
358
+ async with streamablehttp_client(
359
+ url="https://huggingface.co/mcp",
360
+ headers=headers,
361
+ timeout=timedelta(seconds=30),
362
+ terminate_on_close=True
363
+ ) as (read_stream, write_stream, get_session_id):
364
+ diagnostics["connection_test"] = True
365
+ logger.info("Basic connection test passed")
366
+
367
+ # Test initialization
368
+ client = HuggingFaceMCPClient(hf_token, timeout=60)
369
+ try:
370
+ tools = await client.get_all_tools()
371
+ diagnostics["initialization_test"] = True
372
+ diagnostics["tools_test"] = True
373
+ diagnostics["tool_count"] = len(tools)
374
+ logger.info(f"Full diagnostic passed - found {len(tools)} tools")
375
+ except Exception as init_error:
376
+ diagnostics["error"] = str(init_error)
377
+ logger.error(f"Initialization failed: {init_error}")
378
+
379
+ except Exception as conn_error:
380
+ diagnostics["error"] = str(conn_error)
381
+ logger.error(f"Connection test failed: {conn_error}")
382
+
383
+ return diagnostics
384