Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ jobs:
\"repository\": \"${{ github.repository }}\",
\"sha\": \"${{ github.sha }}\",
\"ref\": \"${{ github.ref }}\",
\"actor\": \"${{ github.actor }}\"
\"actor\": \"${{ github.actor }}\",
\"env\": \"production\"
}
}"

Expand Down
67 changes: 39 additions & 28 deletions assemblymcp/initialization.py
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

Expand All @@ -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:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check master_file.stat().st_size > 1000 uses 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 = 1000 at the module level and use it here as suggested.

Suggested change
if master_file.exists() and master_file.stat().st_size > 1000:
if master_file.exists() and master_file.stat().st_size > MIN_MASTER_LIST_SIZE_BYTES:

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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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 RESULT field. Restoring this more detailed error reporting will make it easier to diagnose API-related issues during startup.

Suggested change
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")
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. Keys: {list(data.keys())}")


# 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."""
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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 assembly_client library lacks a public API for reloading specs. This context is valuable for future maintainers. It would be best to include this information in the docstring of _reload_client_maps.

Suggested change
"""Helper to reload client service maps from cache directory."""
"""Helper to reload client service maps from cache directory.
NOTE: This is a workaround that directly modifies internal attributes of the
AssemblyAPIClient instance. It is necessary because the assembly_client library
does not provide a public API to reload specs. If the library is updated,
this should be replaced with a call to the official public method.
"""

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.")
Loading