112112
113113api_client = PTABClient (api_key = settings .uspto_api_key )
114114
115+
116+ def get_api_client () -> PTABClient :
117+ """
118+ Lazily initialize and return the API client.
119+
120+ This ensures the client is properly initialized even in complex async contexts
121+ where the event loop lifecycle may vary between MCP clients.
122+
123+ Returns:
124+ PTABClient instance
125+ """
126+ global api_client
127+ if api_client is None :
128+ logger .info ("Initializing PTAB API client" )
129+ api_client = PTABClient (api_key = settings .uspto_api_key )
130+ return api_client
131+
132+
115133# Initialize field manager with config path
116134config_path = Path (__file__ ).parent .parent .parent / "field_configs.yaml"
117135field_manager = FieldManager (config_path = config_path )
@@ -251,6 +269,12 @@ async def search_trials_minimal(
251269 "count": 2, "results": [...], "context_reduction": {...}}
252270 """
253271 try :
272+ # Ensure API client is initialized (critical fix for async lifecycle issues)
273+ global api_client
274+ if api_client is None :
275+ logger .info ("Initializing API client for trial search" )
276+ api_client = get_api_client ()
277+
254278 # Validate inputs
255279 if trial_number :
256280 trial_number = validate_trial_number (trial_number )
@@ -434,6 +458,12 @@ async def search_trials_balanced(
434458 JSON string with comprehensive trial data (balanced or custom field set)
435459 """
436460 try :
461+ # Ensure API client is initialized (critical fix for async lifecycle issues)
462+ global api_client
463+ if api_client is None :
464+ logger .info ("Initializing API client for trial search" )
465+ api_client = get_api_client ()
466+
437467 # Validate inputs
438468 if trial_number :
439469 trial_number = validate_trial_number (trial_number )
@@ -611,6 +641,12 @@ async def search_trials_complete(
611641 JSON string with complete trial data (all fields or custom field set)
612642 """
613643 try :
644+ # Ensure API client is initialized (critical fix for async lifecycle issues)
645+ global api_client
646+ if api_client is None :
647+ logger .info ("Initializing API client for trial search" )
648+ api_client = get_api_client ()
649+
614650 # Validate inputs (same as minimal)
615651 if trial_number :
616652 trial_number = validate_trial_number (trial_number )
@@ -836,6 +872,12 @@ async def ptab_get_documents(
836872 ]}
837873 """
838874 try :
875+ # Ensure API client is initialized (critical fix for async lifecycle issues)
876+ global api_client
877+ if api_client is None :
878+ logger .info ("Initializing API client for document operations" )
879+ api_client = get_api_client ()
880+
839881 # Validate limit
840882 if limit < 1 or limit > 200 :
841883 raise ValueError ("Limit must be between 1 and 200" )
@@ -980,6 +1022,18 @@ async def ptab_get_documents(
9801022
9811023 except ValueError as e :
9821024 return format_error_response (str (e ), "VALIDATION_ERROR" )
1025+ except RuntimeError as e :
1026+ # Catch async lifecycle errors specifically
1027+ error_msg = str (e )
1028+ if "cannot schedule new futures" in error_msg or "interpreter shutdown" in error_msg :
1029+ logger .error (f"Async lifecycle error in ptab_get_documents: { error_msg } " )
1030+ return json .dumps ({
1031+ "error" : True ,
1032+ "message" : "Operation failed due to async runtime issue. Try restarting the MCP server." ,
1033+ "technical_details" : error_msg
1034+ }, indent = 2 )
1035+ else :
1036+ raise # Re-raise other RuntimeErrors
9831037 except Exception as e :
9841038 logger .error (f"Error in ptab_get_documents: { str (e )} " )
9851039 return format_error_response (str (e ), "API_ERROR" )
@@ -1101,6 +1155,12 @@ async def ptab_get_document_download(
11011155 }
11021156 """
11031157 try :
1158+ # Ensure API client is initialized (critical fix for async lifecycle issues)
1159+ global api_client
1160+ if api_client is None :
1161+ logger .info ("Initializing API client for document download" )
1162+ api_client = get_api_client ()
1163+
11041164 # Validate inputs
11051165 identifier_type = validate_identifier_type (identifier_type )
11061166
@@ -1345,6 +1405,18 @@ async def ptab_get_document_download(
13451405
13461406 except ValueError as e :
13471407 return format_error_response (str (e ), "VALIDATION_ERROR" )
1408+ except RuntimeError as e :
1409+ # Catch async lifecycle errors specifically
1410+ error_msg = str (e )
1411+ if "cannot schedule new futures" in error_msg or "interpreter shutdown" in error_msg :
1412+ logger .error (f"Async lifecycle error in ptab_get_document_download: { error_msg } " )
1413+ return json .dumps ({
1414+ "error" : True ,
1415+ "message" : "Operation failed due to async runtime issue. Try restarting the MCP server." ,
1416+ "technical_details" : error_msg
1417+ }, indent = 2 )
1418+ else :
1419+ raise # Re-raise other RuntimeErrors
13481420 except Exception as e :
13491421 logger .error (f"Error in ptab_get_document_download: { str (e )} " )
13501422 return format_error_response (str (e ), "API_ERROR" )
@@ -1437,6 +1509,12 @@ async def ptab_get_document_content(
14371509 }
14381510 """
14391511 try :
1512+ # Ensure API client is initialized (critical fix for async lifecycle issues)
1513+ global api_client
1514+ if api_client is None :
1515+ logger .info ("Initializing API client for document content extraction" )
1516+ api_client = get_api_client ()
1517+
14401518 # Validate inputs
14411519 identifier_type = validate_identifier_type (identifier_type )
14421520
@@ -1592,14 +1670,23 @@ async def ptab_get_document_content(
15921670 error_msg = ocr_result .get ("message" , "Unknown OCR error" )
15931671 logger .error (f"Mistral OCR extraction failed: { error_msg } " )
15941672
1595- # Return fallback with PyPDF2 result if available
1673+ # Return enhanced error with LLM guidance when both extraction methods fail
15961674 if not extracted_text :
1597- extracted_text = (
1598- f"[Mistral OCR extraction failed: { error_msg } ]\n \n "
1599- f"PyPDF2 result: { extracted_text if extracted_text else '(no text extracted)' } \n \n "
1600- "Note: For scanned PDFs or documents with poor PyPDF2 extraction, "
1601- "configure MISTRAL_API_KEY for improved OCR capabilities."
1602- )
1675+ return json .dumps ({
1676+ "document_id" : document_id ,
1677+ "identifier" : identifier ,
1678+ "text" : "" ,
1679+ "extraction_method" : "PyPDF2 (insufficient)" ,
1680+ "error" : "Document appears to be scanned/image-based. PyPDF2 could not extract meaningful text." ,
1681+ "mistral_api_key_missing" : not ocr_service .mistral_api_key ,
1682+ "llm_guidance" : {
1683+ "explain_to_user" : "Many USPTO PTAB documents are scanned images rather than text-based PDFs. "
1684+ "PyPDF2 can only extract text from text-based PDFs - it cannot read scanned images." ,
1685+ "recommended_solution" : "Configure Mistral API for OCR capability (~$0.001/page, with free tier available)" ,
1686+ "free_tier_info" : "Mistral offers a generous free tier - sign up at https://console.mistral.ai/" ,
1687+ "setup_instructions" : "Set MISTRAL_API_KEY environment variable after obtaining key from Mistral console"
1688+ }
1689+ }, indent = 2 )
16031690 ocr_cost_usd = 0.00
16041691
16051692 # Return result
@@ -1619,6 +1706,18 @@ async def ptab_get_document_content(
16191706
16201707 except ValueError as e :
16211708 return format_error_response (str (e ), "VALIDATION_ERROR" )
1709+ except RuntimeError as e :
1710+ # Catch async lifecycle errors specifically
1711+ error_msg = str (e )
1712+ if "cannot schedule new futures" in error_msg or "interpreter shutdown" in error_msg :
1713+ logger .error (f"Async lifecycle error in ptab_get_document_content: { error_msg } " )
1714+ return json .dumps ({
1715+ "error" : True ,
1716+ "message" : "Operation failed due to async runtime issue. Try restarting the MCP server." ,
1717+ "technical_details" : error_msg
1718+ }, indent = 2 )
1719+ else :
1720+ raise # Re-raise other RuntimeErrors
16221721 except Exception as e :
16231722 logger .error (f"Error in ptab_get_document_content: { str (e )} " )
16241723 return format_error_response (str (e ), "API_ERROR" )
@@ -1710,6 +1809,12 @@ async def search_appeals_minimal(
17101809 "count": 5, "results": [...], "context_reduction": {...}}
17111810 """
17121811 try :
1812+ # Ensure API client is initialized (critical fix for async lifecycle issues)
1813+ global api_client
1814+ if api_client is None :
1815+ logger .info ("Initializing API client for appeal search" )
1816+ api_client = get_api_client ()
1817+
17131818 # Validate inputs
17141819 if appeal_number :
17151820 appeal_number = validate_appeal_number (appeal_number )
@@ -1880,6 +1985,12 @@ async def search_appeals_balanced(
18801985 JSON string with comprehensive appeal data (balanced or custom field set)
18811986 """
18821987 try :
1988+ # Ensure API client is initialized (critical fix for async lifecycle issues)
1989+ global api_client
1990+ if api_client is None :
1991+ logger .info ("Initializing API client for appeal search" )
1992+ api_client = get_api_client ()
1993+
18831994 # Validate inputs
18841995 if appeal_number :
18851996 appeal_number = validate_appeal_number (appeal_number )
@@ -2052,6 +2163,12 @@ async def search_appeals_complete(
20522163 JSON string with complete appeal data (all fields or custom field set)
20532164 """
20542165 try :
2166+ # Ensure API client is initialized (critical fix for async lifecycle issues)
2167+ global api_client
2168+ if api_client is None :
2169+ logger .info ("Initializing API client for appeal search" )
2170+ api_client = get_api_client ()
2171+
20552172 # Validate inputs
20562173 if appeal_number :
20572174 appeal_number = validate_appeal_number (appeal_number )
@@ -2228,6 +2345,12 @@ async def search_interferences_minimal(
22282345 "count": 2, "results": [...], "context_reduction": {...}}
22292346 """
22302347 try :
2348+ # Ensure API client is initialized (critical fix for async lifecycle issues)
2349+ global api_client
2350+ if api_client is None :
2351+ logger .info ("Initializing API client for interference search" )
2352+ api_client = get_api_client ()
2353+
22312354 # Validate inputs
22322355 if interference_number :
22332356 interference_number = validate_interference_number (interference_number )
@@ -2389,6 +2512,12 @@ async def search_interferences_balanced(
23892512 JSON string with comprehensive interference data (balanced or custom field set)
23902513 """
23912514 try :
2515+ # Ensure API client is initialized (critical fix for async lifecycle issues)
2516+ global api_client
2517+ if api_client is None :
2518+ logger .info ("Initializing API client for interference search" )
2519+ api_client = get_api_client ()
2520+
23922521 # Validate inputs
23932522 if interference_number :
23942523 interference_number = validate_interference_number (interference_number )
@@ -2560,6 +2689,12 @@ async def search_interferences_complete(
25602689 JSON string with complete interference data (all fields or custom field set)
25612690 """
25622691 try :
2692+ # Ensure API client is initialized (critical fix for async lifecycle issues)
2693+ global api_client
2694+ if api_client is None :
2695+ logger .info ("Initializing API client for interference search" )
2696+ api_client = get_api_client ()
2697+
25632698 # Validate inputs
25642699 if interference_number :
25652700 interference_number = validate_interference_number (interference_number )
0 commit comments