@@ -360,64 +360,62 @@ async def handle_call_tool(name: str, arguments: Dict[str, Any]) -> CallToolResu
360360
361361 async def _search_resources (self , arguments : Dict [str , Any ]) -> CallToolResult :
362362 """Search for resources."""
363+ query = arguments .get ("query" )
364+ page = arguments .get ("page" , 1 )
365+ per_page = arguments .get ("per_page" , 10 )
363366 try :
364- query = arguments .get ("query" )
365- page = arguments .get ("page" , 1 )
366- per_page = arguments .get ("per_page" , 10 )
367367 sort = arguments .get ("sort" )
368-
369- search_service = SearchService ()
370- results = await search_service .search (
371- q = query ,
372- page = page ,
373- limit = per_page ,
374- sort = sort ,
375- request_query_params = "" ,
376- callback = None ,
368+ params = {
369+ "q" : query ,
370+ "page" : page ,
371+ "per_page" : per_page ,
372+ "sort" : sort ,
373+ }
374+ payload = await self ._api_request (
375+ "/search" , params = {key : value for key , value in params .items () if value is not None }
377376 )
378-
379- # Process each resource to get full details
380- processed_resources = []
381- async with get_async_session ()() as session :
382- for item in results .get ("data" , []):
383- try :
384- # Extract the resource data from the search result
385- resource_dict = item .get ("attributes" , {})
386- if not resource_dict :
387- continue
388-
389- # Process the resource using the same logic as API endpoints
390- from app .api .v1 .utils import process_resource
391-
392- resource_object = await process_resource (resource_dict , session )
393- processed_resources .append (resource_object )
394- except Exception as e :
395- logger .error (f"Error processing search result: { str (e )} " , exc_info = True )
396- continue
397-
398- # Return the full resource objects as JSON
399- return CallToolResult (
400- content = [
401- TextContent (
402- type = "text" ,
403- text = json .dumps (
404- {
405- "query" : query ,
406- "total_results" : len (results .get ("data" , [])),
407- "page" : page ,
408- "per_page" : per_page ,
409- "resources" : processed_resources ,
410- },
411- indent = 2 ,
412- ),
413- )
414- ]
377+ response_data = payload .get ("data" )
378+ response_meta = response_data .get ("meta" , {}) if isinstance (response_data , dict ) else {}
379+ resources = response_data .get ("data" , []) if isinstance (response_data , dict ) else []
380+
381+ if payload ["status_code" ] >= 400 :
382+ payload ["query" ] = query
383+ return await self ._tool_result_json (payload , is_error = True )
384+
385+ return await self ._tool_result_json (
386+ {
387+ "query" : query ,
388+ "total_results" : response_meta .get ("totalCount" , len (resources )),
389+ "total_pages" : response_meta .get ("totalPages" ),
390+ "page" : response_meta .get ("currentPage" , page ),
391+ "per_page" : response_meta .get ("perPage" , per_page ),
392+ "spelling_suggestions" : response_meta .get ("spellingSuggestions" , []),
393+ "resources" : resources ,
394+ "links" : (
395+ response_data .get ("links" , {}) if isinstance (response_data , dict ) else {}
396+ ),
397+ "source" : {
398+ "transport" : "http" ,
399+ "url" : payload .get ("url" ),
400+ "status_code" : payload .get ("status_code" ),
401+ },
402+ }
415403 )
416404 except Exception as e :
417405 logger .error (f"Error in _search_resources: { e } " , exc_info = True )
418- return CallToolResult (
419- content = [TextContent (type = "text" , text = f"Error searching resources: { str (e )} " )],
420- isError = True ,
406+ return await self ._tool_result_json (
407+ {
408+ "error" : "Search request failed" ,
409+ "detail" : str (e ),
410+ "query" : query ,
411+ "page" : page ,
412+ "per_page" : per_page ,
413+ "source" : {
414+ "transport" : "http" ,
415+ "url" : f"{ self ._public_api_base ()} /api/v1/search" ,
416+ },
417+ },
418+ is_error = True ,
421419 )
422420
423421 async def _get_resource (self , arguments : Dict [str , Any ]) -> CallToolResult :
@@ -622,7 +620,7 @@ async def _get_resource_viewer(self, arguments: Dict[str, Any]) -> CallToolResul
622620 embed = arguments .get ("embed" , False )
623621
624622 # Build the record URL for the viewer
625- base_url = "http://localhost:8000"
623+ base_url = self . _public_api_base ()
626624 record_url = f"{ base_url } /api/v1/resources/{ resource_id } /metadata"
627625
628626 # Create the HTML content
@@ -677,7 +675,10 @@ async def _get_resource_viewer(self, arguments: Dict[str, Any]) -> CallToolResul
677675
678676 def _public_api_base (self ) -> str :
679677 """Resolve base URL for calling this API over HTTP."""
680- base = os .getenv ("APPLICATION_URL" , "http://localhost:8000" ).rstrip ("/" )
678+ base = os .getenv ("BTAA_GEOSPATIAL_API_BASE_URL" ) or os .getenv (
679+ "APPLICATION_URL" , "http://localhost:8000"
680+ )
681+ base = base .rstrip ("/" )
681682 if base .endswith ("/api/v1" ):
682683 base = base [: - len ("/api/v1" )]
683684 return base
@@ -967,7 +968,8 @@ async def run_mcp_websocket_server(websocket):
967968 try :
968969 data = json .loads (message )
969970 response = await handle_mcp_message (data )
970- await websocket .send_text (json .dumps (response ))
971+ if response is not None :
972+ await websocket .send_text (json .dumps (response ))
971973 except json .JSONDecodeError :
972974 await websocket .send_text (
973975 json .dumps (
@@ -987,12 +989,30 @@ async def run_mcp_websocket_server(websocket):
987989 logging .error (f"WebSocket error: { e } " )
988990
989991
990- async def handle_mcp_message (data : Dict [str , Any ]) -> Dict [str , Any ]:
992+ async def handle_mcp_message (data : Dict [str , Any ]) -> Dict [str , Any ] | None :
991993 """Handle MCP protocol messages."""
994+ if not isinstance (data , dict ):
995+ return {
996+ "jsonrpc" : "2.0" ,
997+ "id" : None ,
998+ "error" : {"code" : - 32600 , "message" : "Invalid request" },
999+ }
1000+
9921001 method = data .get ("method" )
9931002 msg_id = data .get ("id" )
9941003 params = data .get ("params" , {})
9951004
1005+ if not method or not isinstance (method , str ):
1006+ return {
1007+ "jsonrpc" : "2.0" ,
1008+ "id" : msg_id ,
1009+ "error" : {"code" : - 32600 , "message" : "Invalid request" },
1010+ }
1011+
1012+ # Ignore notifications that do not expect a reply.
1013+ if method in {"notifications/initialized" , "$/cancelRequest" } and msg_id is None :
1014+ return None
1015+
9961016 if method == "initialize" :
9971017 return {
9981018 "jsonrpc" : "2.0" ,
@@ -1004,6 +1024,9 @@ async def handle_mcp_message(data: Dict[str, Any]) -> Dict[str, Any]:
10041024 },
10051025 }
10061026
1027+ elif method == "ping" :
1028+ return {"jsonrpc" : "2.0" , "id" : msg_id , "result" : {}}
1029+
10071030 elif method == "tools/list" :
10081031 return {"jsonrpc" : "2.0" , "id" : msg_id , "result" : {"tools" : mcp_service .tool_specs }}
10091032
@@ -1085,8 +1108,8 @@ def get_mcp_service_info() -> dict[str, Any]:
10851108 "connections" : {
10861109 "stdio" : {
10871110 "type" : "stdio" ,
1088- "command" : "python " ,
1089- "args" : ["-m" , "app.services.mcp_service " ],
1111+ "command" : "python3 " ,
1112+ "args" : ["mcp/run_mcp_service.py " ],
10901113 },
10911114 "websocket" : {"type" : "websocket" , "url" : "/api/v1/mcp/ws" },
10921115 },
0 commit comments