Files
zeekay 6326973161 feat: add hanzo-tools-api package and hanzo.infra SDK
Add generic REST API tool for calling any API via OpenAPI specs:
- 30+ built-in provider configs with env var auto-detection
- 1100+ auto-generated providers from APIs.guru + oapis.org
- Secure credential management with pluggable storage
- OpenAPI spec parsing with ETag caching
- Agent-friendly interface with search, overview, call actions

Add hanzo.infra unified infrastructure SDK:
- Vector (Qdrant), KV (Redis/Valkey), DocumentDB (MongoDB)
- Storage (S3/MinIO), Search (Meilisearch), PubSub (NATS)
- Tasks (Temporal), Queues, Cron, Functions (Nuclio)
- Async clients with lazy initialization and health checks
- Optional deps via [infra-*] extras

Bump hanzo-mcp to v0.11.4 with api tool integration.
2026-01-19 23:00:17 -08:00

141 lines
4.4 KiB
Python

#!/usr/bin/env python3
"""Pre-download and cache popular OpenAPI specs for faster startup.
Usage:
python scripts/preload_specs.py # Download top 50 specs
python scripts/preload_specs.py --all # Download all specs with URLs
python scripts/preload_specs.py --list # List available specs
python scripts/preload_specs.py github stripe # Download specific specs
"""
import asyncio
import argparse
import sys
from pathlib import Path
# Add parent to path for local development
sys.path.insert(0, str(Path(__file__).parent.parent))
from hanzo_tools.api.client import APIClient
from hanzo_tools.api.providers import list_providers_with_specs, get_provider_config
# Top 50 most popular APIs to pre-cache
POPULAR_APIS = [
# AI/ML
"openai", "anthropic", "together", "groq", "mistral", "cohere", "replicate",
# Developer
"github", "gitlab", "bitbucket", "vercel", "netlify", "fly", "railway", "render",
# Cloud
"cloudflare", "digitalocean", "hetzner", "linode", "vultr",
# Payment
"stripe", "paypal", "adyen-com-accountservice", "square",
# Communication
"twilio", "sendgrid", "resend", "slack", "discord",
# Databases
"supabase", "neon", "planetscale", "mongodb", "redis",
# Search
"algolia", "elasticsearch", "meilisearch", "typesense",
# Monitoring
"datadog", "sentry", "newrelic",
# Other popular
"notion", "airtable", "hubspot", "shopify", "jira",
]
async def preload_specs(
providers: list[str] | None = None,
concurrent: int = 10,
verbose: bool = True,
) -> dict[str, bool]:
"""Pre-download and cache OpenAPI specs.
Args:
providers: List of provider names, or None for POPULAR_APIS
concurrent: Number of concurrent downloads
verbose: Print progress
Returns:
Dict mapping provider name to success status
"""
client = APIClient()
if providers is None:
providers = POPULAR_APIS
# Filter to only those with spec URLs
valid_providers = []
for p in providers:
config = get_provider_config(p)
if config and config.spec_url:
valid_providers.append(p)
elif verbose:
print(f" ⚠ {p}: no spec URL configured")
if verbose:
print(f"Pre-loading {len(valid_providers)} OpenAPI specs...")
print()
results: dict[str, bool] = {}
semaphore = asyncio.Semaphore(concurrent)
async def fetch_one(provider: str) -> tuple[str, bool]:
async with semaphore:
try:
await client.spec(provider)
if verbose:
print(f" ✓ {provider}")
return provider, True
except Exception as e:
if verbose:
print(f" ✗ {provider}: {e}")
return provider, False
tasks = [fetch_one(p) for p in valid_providers]
for coro in asyncio.as_completed(tasks):
provider, success = await coro
results[provider] = success
if verbose:
success_count = sum(1 for v in results.values() if v)
print()
print(f"Downloaded {success_count}/{len(results)} specs")
print(f"Cached at: {client._spec_cache.cache_dir}")
return results
async def main():
parser = argparse.ArgumentParser(description="Pre-download OpenAPI specs")
parser.add_argument("providers", nargs="*", help="Specific providers to download")
parser.add_argument("--all", action="store_true", help="Download all available specs")
parser.add_argument("--list", action="store_true", help="List available specs")
parser.add_argument("-n", "--concurrent", type=int, default=10, help="Concurrent downloads")
parser.add_argument("-q", "--quiet", action="store_true", help="Quiet mode")
args = parser.parse_args()
if args.list:
specs = list_providers_with_specs()
print(f"Available specs ({len(specs)}):")
for i, name in enumerate(specs, 1):
config = get_provider_config(name)
display = config.display_name if config else name
print(f" {i:4}. {name}: {display}")
return
providers = None
if args.providers:
providers = args.providers
elif args.all:
providers = list_providers_with_specs()
await preload_specs(
providers=providers,
concurrent=args.concurrent,
verbose=not args.quiet,
)
if __name__ == "__main__":
asyncio.run(main())