-
Notifications
You must be signed in to change notification settings - Fork 0
fix: resolve service discovery issues and enhance deployment #53
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,5 +1,7 @@ | ||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||
| import logging | ||||||||||||||||||||||||||||
| import shutil | ||||||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| from assembly_client.api import AssemblyAPIClient | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
@@ -13,63 +15,72 @@ | |||||||||||||||||||||||||||
| async def ensure_master_list(client: AssemblyAPIClient) -> None: | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| Ensure the master list of APIs (all_apis.json) exists in the cache. | ||||||||||||||||||||||||||||
| If not, download it from the OPENSRVAPI service. | ||||||||||||||||||||||||||||
| 1. Try to copy from the bundled specs in the package. | ||||||||||||||||||||||||||||
| 2. If not found, try to download from the OPENSRVAPI service. | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| Raises: | ||||||||||||||||||||||||||||
| RuntimeError: If the master list cannot be downloaded or is invalid. | ||||||||||||||||||||||||||||
| RuntimeError: If the master list cannot be obtained. | ||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||
| cache_dir = client.spec_parser.cache_dir | ||||||||||||||||||||||||||||
| master_file = cache_dir / "all_apis.json" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| if master_file.exists(): | ||||||||||||||||||||||||||||
| # Ensure cache directory exists | ||||||||||||||||||||||||||||
| cache_dir.mkdir(parents=True, exist_ok=True) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # 1. Check if master file already exists in cache | ||||||||||||||||||||||||||||
| if master_file.exists() and master_file.stat().st_size > 1000: | ||||||||||||||||||||||||||||
| logger.info(f"Master list found at {master_file}") | ||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| logger.info(f"Master list not found at {master_file}. Downloading...") | ||||||||||||||||||||||||||||
| # 2. Try to copy from bundled specs (AssemblyMCP/assemblymcp/specs/all_apis.json) | ||||||||||||||||||||||||||||
| bundled_file = Path(__file__).parent / "specs" / "all_apis.json" | ||||||||||||||||||||||||||||
| if bundled_file.exists(): | ||||||||||||||||||||||||||||
| logger.info(f"Copying bundled master list from {bundled_file} to {master_file}") | ||||||||||||||||||||||||||||
| shutil.copy(bundled_file, master_file) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Reload maps after copy | ||||||||||||||||||||||||||||
| _reload_client_maps(client, cache_dir) | ||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # 3. Fallback: Download from API (Requires ASSEMBLY_API_KEY) | ||||||||||||||||||||||||||||
| logger.info(f"Master list not found. Attempting to download...") | ||||||||||||||||||||||||||||
| if not client.api_key: | ||||||||||||||||||||||||||||
| logger.warning("ASSEMBLY_API_KEY is missing. Only limited tools will be available.") | ||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||
| # Directly construct URL since we can't use client.get_data | ||||||||||||||||||||||||||||
| # (it depends on the master list for ID resolution) | ||||||||||||||||||||||||||||
| url = f"{client.BASE_URL}/{SERVICE_LIST_API_ID}" | ||||||||||||||||||||||||||||
| params = { | ||||||||||||||||||||||||||||
| "KEY": client.api_key, | ||||||||||||||||||||||||||||
| "Type": "json", | ||||||||||||||||||||||||||||
| "pIndex": 1, | ||||||||||||||||||||||||||||
| "pSize": 1000, # Fetch all (there are ~270, so 1000 is safe) | ||||||||||||||||||||||||||||
| "pSize": 1000, | ||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| logger.info(f"Fetching service list from {url}") | ||||||||||||||||||||||||||||
| response = await client.client.get(url, params=params) | ||||||||||||||||||||||||||||
| response.raise_for_status() | ||||||||||||||||||||||||||||
| data = response.json() | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Validate response | ||||||||||||||||||||||||||||
| if SERVICE_LIST_API_ID not in data: | ||||||||||||||||||||||||||||
| if "RESULT" in data: | ||||||||||||||||||||||||||||
| code = data["RESULT"].get("CODE") | ||||||||||||||||||||||||||||
| msg = data["RESULT"].get("MESSAGE") | ||||||||||||||||||||||||||||
| raise RuntimeError(f"Failed to fetch master list: {code} - {msg}") | ||||||||||||||||||||||||||||
| raise RuntimeError(f"Invalid response format for master list: {data.keys()}") | ||||||||||||||||||||||||||||
| raise RuntimeError(f"Invalid response format for master list") | ||||||||||||||||||||||||||||
|
Comment on lines
65
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The error handling for a failed API download has been simplified, but this removes potentially useful debugging information. The previous implementation extracted the error code and message from the API's
Suggested change
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Save to file | ||||||||||||||||||||||||||||
| with open(master_file, "w", encoding="utf-8") as f: | ||||||||||||||||||||||||||||
| json.dump(data, f, ensure_ascii=False, indent=2) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| logger.info(f"Successfully saved master list to {master_file}") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Reload the client's service maps | ||||||||||||||||||||||||||||
| # NOTE: This is a workaround as the assembly_client library doesn't provide | ||||||||||||||||||||||||||||
| # a public API to reload specs. If the library is updated, consider using | ||||||||||||||||||||||||||||
| # a public method instead of directly modifying internal attributes. | ||||||||||||||||||||||||||||
| from assembly_client.parser import load_service_map, load_service_metadata | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| client.service_map = load_service_map(cache_dir) | ||||||||||||||||||||||||||||
| client.name_to_id = {name: sid for sid, name in client.service_map.items()} | ||||||||||||||||||||||||||||
| client.service_metadata = load_service_metadata(cache_dir) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| logger.info(f"Reloaded service map: {len(client.service_map)} services found.") | ||||||||||||||||||||||||||||
| _reload_client_maps(client, cache_dir) | ||||||||||||||||||||||||||||
| logger.info(f"Successfully downloaded and reloaded master list.") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||
| logger.error(f"Failed to initialize master list: {e}") | ||||||||||||||||||||||||||||
| # Re-raise the exception to prevent the server from starting in a broken state. | ||||||||||||||||||||||||||||
| raise | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def _reload_client_maps(client: AssemblyAPIClient, cache_dir: Path) -> None: | ||||||||||||||||||||||||||||
| """Helper to reload client service maps from cache directory.""" | ||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This new helper function is a good refactoring. However, the original code included an important comment explaining that modifying the client's internal attributes is a workaround because the
Suggested change
|
||||||||||||||||||||||||||||
| from assembly_client.parser import load_service_map, load_service_metadata | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| client.service_map = load_service_map(cache_dir) | ||||||||||||||||||||||||||||
| client.name_to_id = {name: sid for sid, name in client.service_map.items()} | ||||||||||||||||||||||||||||
| client.service_metadata = load_service_metadata(cache_dir) | ||||||||||||||||||||||||||||
| logger.info(f"Reloaded service map: {len(client.service_map)} services found.") | ||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The check
master_file.stat().st_size > 1000uses a magic number (1000). This makes the code harder to understand and maintain. It's better to define this value as a constant with a descriptive name. This clarifies the intent of the check (e.g., ensuring the file is not empty or truncated) and makes it easier to change if needed.Please define
MIN_MASTER_LIST_SIZE_BYTES = 1000at the module level and use it here as suggested.