{"id":48773677,"url":"https://github.com/sonaiengine/api-to-tools","last_synced_at":"2026-04-15T13:00:50.462Z","repository":{"id":350133435,"uuid":"1205443346","full_name":"SonAIengine/api-to-tools","owner":"SonAIengine","description":"Universal library that converts any API (OpenAPI, WSDL/SOAP, GraphQL, gRPC) into LLM-callable tool definitions","archived":false,"fork":false,"pushed_at":"2026-04-12T18:16:04.000Z","size":1529,"stargazers_count":0,"open_issues_count":1,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-13T11:41:01.678Z","etag":null,"topics":["ai-agent","anthropic","api-crawler","api-discovery","claude","function-calling","graphql","grpc","llm","llm-tools","mcp","mcp-server","nexacro","openapi","playwright","python","soap","swagger","tool-calling","wsdl"],"latest_commit_sha":null,"homepage":"https://pypi.org/project/api-to-tools/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SonAIengine.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-09T01:07:18.000Z","updated_at":"2026-04-12T18:16:07.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/SonAIengine/api-to-tools","commit_stats":null,"previous_names":["sonaiengine/api-to-tools"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/SonAIengine/api-to-tools","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SonAIengine%2Fapi-to-tools","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SonAIengine%2Fapi-to-tools/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SonAIengine%2Fapi-to-tools/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SonAIengine%2Fapi-to-tools/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SonAIengine","download_url":"https://codeload.github.com/SonAIengine/api-to-tools/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SonAIengine%2Fapi-to-tools/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31795334,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T11:13:53.975Z","status":"ssl_error","status_checked_at":"2026-04-14T11:13:53.299Z","response_time":153,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agent","anthropic","api-crawler","api-discovery","claude","function-calling","graphql","grpc","llm","llm-tools","mcp","mcp-server","nexacro","openapi","playwright","python","soap","swagger","tool-calling","wsdl"],"created_at":"2026-04-13T11:32:49.829Z","updated_at":"2026-04-14T12:00:57.096Z","avatar_url":"https://github.com/SonAIengine.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# api-to-tools\n\nUniversal library that converts **any API into LLM-callable tool definitions**.\n\nGive it a website URL (with or without credentials) and it returns a list of\nTools that can be handed directly to Claude, OpenAI, Gemini, or an MCP server — no\nmanual tool wiring required.\n\n[![PyPI](https://img.shields.io/pypi/v/api-to-tools.svg)](https://pypi.org/project/api-to-tools/)\n[![CI](https://github.com/SonAIengine/api-to-tools/actions/workflows/ci.yml/badge.svg)](https://github.com/SonAIengine/api-to-tools/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n\n---\n\n## What it does\n\n```python\nfrom api_to_tools import discover, discover_all, AuthConfig\n\n# Public OpenAPI / Swagger site\ntools = discover(\"https://petstore.swagger.io\")\n# → 20 tools\n\n# Browser network recording (no Swagger at all)\ntools = discover(\"recording.har\")\n# → Tools inferred from actual HTTP traffic\n\n# Multiple sources merged\ntools = discover_all([\n    \"https://api.example.com/openapi.json\",\n    \"https://internal.example.com/api-docs\",\n    \"recording.har\",\n])\n\n# Private admin panel (login → auto-discover backend Swagger)\ntools = discover(\n    \"https://admin.example.com/\",\n    auth=AuthConfig(type=\"cookie\", username=\"admin\", password=\"admin\"),\n)\n# → 1090 tools\n```\n\nOne function call, one URL, one account — you get a complete tool catalog.\n\n---\n\n## Installation\n\n```bash\npip install api-to-tools\n\n# Optional extras\npip install 'api-to-tools[crawler]'     # Playwright browser crawling\npip install 'api-to-tools[websocket]'   # WebSocket/SSE executor\npython -m playwright install chromium   # for crawler\n```\n\nRequires Python 3.10+.\n\n---\n\n## Supported sources\n\n| Source | Status | Notes |\n|--------|:------:|-------|\n| OpenAPI 3.0 / 3.1 | ✅ | Full body DTO, enum, response schema, security scheme extraction |\n| Swagger 2.0 (legacy) | ✅ | `parameters[].in=body`, `responses.200.schema`, `securityDefinitions` |\n| WSDL / SOAP | ✅ | zeep-based, input/output schemas |\n| GraphQL | ✅ | Introspection, selection set auto-build |\n| gRPC / Protobuf | ✅ | `.proto` file parsing, streaming detection, executor with reflection |\n| AsyncAPI 2.x / 3.x | ✅ | WebSocket, MQTT, Kafka, AMQP channels → Tools |\n| HAR files | ✅ | Browser DevTools network recordings → Tools with inferred schemas |\n| Traffic proxy | ✅ | Built-in HTTP proxy → auto-record → Tools |\n| Authenticated Swagger | ✅ | Login → guess backend → Bearer probe |\n| Nexacro / SSV | ✅ | Korean enterprise legacy (Lotte, 금융권 등) |\n| JS bundle scanning | ✅ | Static analysis when no spec exists |\n| Playwright crawler | ✅ | Dynamic SPA discovery with safe mode |\n| CDP crawler | ✅ | Chrome DevTools Protocol (no Playwright needed) |\n\n---\n\n## How discovery works\n\n`discover()` tries sources in priority order and stops at the first one that\nworks:\n\n```\n1. Direct spec URL (OpenAPI, WSDL, GraphQL, HAR, AsyncAPI)\n2. Nexacro platform detection  → Nexacro crawler + SSV parser\n3. Well-known paths probe     → /openapi.json, /swagger.json, /api-docs, ...\n4. Authenticated Swagger      → login → guess backend → Bearer probe\n5. JS bundle static scan      (opt-in: scan_js=True)\n6. Playwright dynamic crawl   (opt-in: crawl=True)\n```\n\nParallel probing and path priority mean most public APIs are discovered in\nunder 2 seconds.\n\n---\n\n## CLI\n\n```bash\n# Summarize an API\napi-to-tools info https://admin.example.com \\\n  --login-user admin --login-pass admin\n\n# List tools filtered by tag\napi-to-tools list https://admin.example.com \\\n  --login-user admin --login-pass admin \\\n  --tag \"회원 정보 관리\"\n\n# Export tool definitions\napi-to-tools export https://admin.example.com \\\n  --login-user admin --login-pass admin \\\n  --format anthropic \u003e tools.json\n\n# Start an MCP server that exposes discovered APIs\napi-to-tools serve https://admin.example.com \\\n  --login-user admin --login-pass admin \\\n  --name my-admin-api\n```\n\n### Authentication options (any subcommand)\n\n```bash\n--bearer TOKEN            # Bearer token\n--basic USER:PASS         # HTTP Basic\n--api-key NAME=VALUE      # API key (header/query)\n--cookie NAME=VALUE       # Direct cookie (repeatable)\n--header \"Name: Value\"    # Custom header (repeatable)\n--login URL               # Form login URL\n--login-user USERNAME     # Login username (shortcut for cookie login)\n--login-pass PASSWORD     # Login password\n```\n\n### Discovery modes\n\n```bash\n--scan-js       # Static analysis of JavaScript bundles\n--crawl         # Playwright browser crawl\n--backend auto  # auto | system | playwright | lightpanda\n--no-safe-mode  # DANGEROUS: allows destructive requests to reach server\n```\n\n---\n\n## Python API\n\n### Basic usage\n\n```python\nfrom api_to_tools import discover, execute, AuthConfig\n\ntools = discover(\"https://date.nager.at/openapi/v3.json\")\nprint(f\"Found {len(tools)} tools\")\n\n# Execute a tool directly\ntool = next(t for t in tools if \"PublicHolidays\" in t.name)\nresult = execute(tool, {\"year\": \"2026\", \"countryCode\": \"KR\"})\nprint(result.data)  # → list of 15 holidays\n```\n\n### Multiple sources\n\n```python\nfrom api_to_tools import discover_all\n\ntools = discover_all([\n    \"https://api.example.com/openapi.json\",\n    \"https://internal.example.com/api-docs\",\n    \"recording.har\",\n])\n# Merges tools, deduplicates by (endpoint, method), resolves name collisions\n```\n\n### HAR file parsing\n\n```python\n# Export HAR from browser DevTools → Network → Export HAR\ntools = discover(\"recording.har\")\n\n# Or parse directly\nfrom api_to_tools.parsers.har import parse_har\ntools = parse_har(\"recording.har\")\n```\n\n### Traffic proxy capture\n\n```python\nfrom api_to_tools.proxy import TrafficRecorder\n\n# Start proxy, browse the site, stop → Tools\nwith TrafficRecorder(port=8080, target_host=\"api.example.com\") as recorder:\n    print(\"Configure browser proxy to http://localhost:8080\")\n    input(\"Press Enter when done browsing...\")\n\ntools = recorder.to_tools()\nrecorder.save_har(\"captured.har\")  # save for later\n\n# Or quick capture for a fixed duration\nfrom api_to_tools.proxy import capture_traffic\ntools = capture_traffic(port=8080, duration=60, target_host=\"api.example.com\")\n```\n\n### Caching\n\n```python\n# Cache discovery results for 5 minutes\ntools = discover(\"https://api.example.com/docs\", cache_ttl=300)\ntools = discover(\"https://api.example.com/docs\", cache_ttl=300)  # instant cache hit\n\n# Manual invalidation\nfrom api_to_tools.cache import get_discover_cache\nget_discover_cache().invalidate(\"https://api.example.com/docs\")\n```\n\n### Authentication\n\n```python\n# Basic Auth\nAuthConfig(type=\"basic\", username=\"admin\", password=\"secret\")\n\n# Bearer token\nAuthConfig(type=\"bearer\", token=\"eyJ...\")\n\n# API key (header or query)\nAuthConfig(type=\"api_key\", key=\"X-API-Key\", value=\"abc\", location=\"header\")\n\n# Form login → session cookie\nAuthConfig(type=\"cookie\", username=\"user\", password=\"pass\")\n\n# OAuth2 client credentials (auto-renewal on expiry)\nAuthConfig(\n    type=\"oauth2_client\",\n    token_url=\"https://auth.example.com/token\",\n    client_id=\"id\",\n    client_secret=\"secret\",\n    scope=\"read write\",\n)\n\n# OAuth2 with refresh token\nAuthConfig(\n    type=\"oauth2_client\",\n    token_url=\"https://auth.example.com/token\",\n    client_id=\"id\",\n    client_secret=\"secret\",\n    refresh_token=\"rt_...\",\n)\n\n# Custom headers\nAuthConfig(type=\"custom\", headers={\"Authorization\": \"Custom xyz\"})\n\n# Disable TLS verification (self-signed certs)\nAuthConfig(type=\"bearer\", token=\"...\", verify_ssl=False)\n```\n\nOAuth2 tokens are automatically refreshed before expiry. On 401 responses,\n`execute()` refreshes the token and retries once.\n\n### LLM integration\n\n```python\n# Claude / Anthropic\nfrom api_to_tools import to_anthropic_tools\nresponse = client.messages.create(\n    model=\"claude-sonnet-4-5\",\n    tools=to_anthropic_tools(tools),\n    messages=[...],\n)\n\n# OpenAI function calling\nfrom api_to_tools import to_function_calling\nopenai_tools = to_function_calling(tools)\n\n# Google Gemini / Vertex AI\nfrom api_to_tools import to_gemini_tools\ngemini_tools = to_gemini_tools(tools)\n\n# AWS Bedrock\nfrom api_to_tools import to_bedrock_tools\nbedrock_tools = to_bedrock_tools(tools)\n\n# LangChain\nfrom api_to_tools import to_langchain_tools\nlc_tools = to_langchain_tools(tools)\n\n# MCP server (stdio)\nfrom api_to_tools.adapters.mcp_adapter import create_mcp_server\nserver = create_mcp_server(tools, name=\"my-api\")\nserver.run(transport=\"stdio\")\n```\n\n### OpenAPI spec export\n\n```python\nfrom api_to_tools import to_openapi_spec\n\n# Generate OpenAPI 3.0 spec from discovered tools\nspec = to_openapi_spec(tools, title=\"My API\", version=\"1.0.0\")\n\n# Save as JSON\nfrom api_to_tools.adapters.openapi_export import to_openapi_json\nwith open(\"openapi.json\", \"w\") as f:\n    f.write(to_openapi_json(tools, title=\"My API\"))\n```\n\n### SDK code generation\n\n```python\nfrom api_to_tools import generate_python_sdk, generate_typescript_sdk\n\n# Generate typed Python client\ncode = generate_python_sdk(tools, class_name=\"PetStoreClient\")\nwith open(\"petstore_client.py\", \"w\") as f:\n    f.write(code)\n\n# Generate TypeScript client\nts_code = generate_typescript_sdk(tools, class_name=\"PetStoreClient\")\nwith open(\"petstore_client.ts\", \"w\") as f:\n    f.write(ts_code)\n```\n\n### Smoke testing\n\n```python\nfrom api_to_tools import run_smoke_tests, generate_test_code\n\n# Run smoke tests (GET only by default)\nreport = run_smoke_tests(tools, auth=auth)\nprint(report.summary)  # \"15 passed, 2 failed, 8 skipped / 25 total\"\n\n# Include mutations (POST/PUT/DELETE)\nreport = run_smoke_tests(tools, include_mutations=True)\n\n# Dry run (no network calls)\nreport = run_smoke_tests(tools, dry_run=True)\n\n# Generate pytest file\ncode = generate_test_code(tools)\nwith open(\"test_api_smoke.py\", \"w\") as f:\n    f.write(code)\n```\n\n### Filters\n\n```python\ntools = discover(\n    url,\n    auth=auth,\n    tags=[\"users\", \"orders\"],             # only specific tags\n    methods=[\"GET\"],                      # only GET\n    path_filter=r\"/api/v2/.*\",            # regex on endpoint\n    base_url=\"https://prod.example.com\",  # override base URL\n)\n```\n\n### Utilities\n\n```python\nfrom api_to_tools import summarize, group_by_tag, search_tools\n\nsummary = summarize(tools)\n# {\"total\": 1090, \"by_method\": {...}, \"by_tag\": {...}, \"by_protocol\": {...}}\n\ngroups = group_by_tag(tools)\norder_tools = search_tools(tools, \"order\")\n```\n\n---\n\n## Safe mode\n\nWhen crawling a live production site, `safe_mode=True` (default) intercepts\nmutation requests (POST/PUT/DELETE/PATCH) after login and fakes a success\nresponse. The request is still captured for discovery, but **never reaches\nthe server** — so `deleteUser`, `save`, `send` etc. can't cause damage.\n\n```python\ndiscover(url, auth=auth, crawl=True, safe_mode=True)\n```\n\nA smart heuristic allows read-style POSTs (`getUserList`, `searchItems`,\nauth endpoints) to pass through, since they're common in RPC-style APIs.\n\n---\n\n## Rate limiting\n\nAll outgoing requests are rate-limited per domain to prevent overwhelming\ntarget servers:\n\n- **Discovery probing**: 20 requests/second\n- **API execution**: 10 requests/second\n\nRate limiting is automatic and requires no configuration.\n\n---\n\n## Architecture\n\n```\napi_to_tools/\n├── core.py              # discover, discover_all, to_tools, execute\n├── types.py             # Tool, ToolParameter, AuthConfig, ExecutionResult, …\n├── constants.py         # Timeouts, rate limits, well-known paths\n├── auth.py              # Auth config → HTTP headers/cookies, TokenManager\n├── cache.py             # TTL-based discover() result cache\n├── rate_limiter.py      # Per-domain token bucket rate limiter\n├── testing.py           # Smoke test runner + pytest code generator\n├── codegen.py           # Python / TypeScript SDK code generator\n├── proxy.py             # HTTP traffic capture proxy → HAR → Tools\n│\n├── detector/\n│   ├── __init__.py            # Spec type detection, parallel probing\n│   └── swagger_discovery.py   # Authenticated backend Swagger hunting\n│\n├── parsers/\n│   ├── openapi.py       # OpenAPI 3.x + Swagger 2.0 + security schemes\n│   ├── asyncapi.py      # AsyncAPI 2.x / 3.x\n│   ├── har.py           # HAR file parser (browser recordings)\n│   ├── wsdl.py          # WSDL/SOAP via zeep\n│   ├── graphql.py       # GraphQL introspection\n│   ├── grpc.py          # .proto parsing\n│   ├── ssv.py           # Nexacro SSV format\n│   ├── nexacro.py       # Nexacro-specific crawler\n│   ├── crawler.py       # Generic Playwright crawler\n│   ├── cdp_crawler.py   # Chrome DevTools Protocol crawler\n│   ├── static_spa.py    # Browserless JS bundle analysis\n│   ├── jsbundle.py      # Static JS bundle scanner\n│   ├── _param_builder.py  # Shared ToolParameter helpers\n│   └── _browser_utils.py  # Shared Playwright helpers\n│\n├── executors/\n│   ├── rest.py          # REST + Nexacro SSV execution\n│   ├── soap.py          # SOAP calls via zeep\n│   ���── graphql.py       # GraphQL query execution\n│   ├── grpc_exec.py     # gRPC execution (reflection + JSON fallback)\n│   └── async_exec.py    # WebSocket / SSE execution\n│\n└── adapters/\n    ├── formats.py         # OpenAI / Anthropic / Gemini / Bedrock / LangChain\n    ├── openapi_export.py  # Tool → OpenAPI 3.0 spec (reverse export)\n    └── mcp_adapter.py     # MCP server generation\n```\n\n---\n\n## Development\n\n```bash\ngit clone https://github.com/SonAIengine/api-to-tools.git\ncd api-to-tools\npip install -e '.[dev]'\n\npytest              # 350 unit tests\nruff check src/     # lint\n```\n\n### Debug logging\n\n```python\nfrom api_to_tools import enable_debug_logging\nenable_debug_logging()\n```\n\n---\n\n## License\n\nMIT © SonAIengine\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsonaiengine%2Fapi-to-tools","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsonaiengine%2Fapi-to-tools","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsonaiengine%2Fapi-to-tools/lists"}