Probe a base URL to discover its API surface: type, auth, endpoints, rate limits, CORS, and server fingerprint.
The probe also supports discovering MCP (Model Context Protocol) server surfaces — tools, resources, and prompts with full input schemas. This lets you evaluate an MCP server before configuring it in Hermes.
-
Base URL wrong or too specific. https://api.example.com is correct; https://api.example.com/v1/users/123 is too specific — the probe needs the root to discover paths. Strip trailing paths.
OpenAI-compatible APIs: If the base URL is an inference endpoint like https://provider.ai/v1 or https://provider.ai/zen/go/v1, the probe will find nothing because discovery paths (/openapi.json, /docs, etc.) are served from the domain root, not the inference prefix. Always probe the domain root (https://provider.ai) to find the OpenAPI spec and full API surface. The inference endpoints (/v1/chat/completions, /v1/models) are usually a small slice of a larger platform API.
-
GraphQL introspection disabled. Many production GraphQL APIs disable introspection for security. The probe will show status 200/400/403 with "introspection disabled" — this confirms GraphQL exists but the schema is hidden. Try sending a simple query like {"query": "{ __typename }"} to confirm.
-
Auth-gated APIs return 404 instead of 401. Some APIs return 404 for unauthenticated requests to hide endpoint existence. If everything is 404, try providing --auth credentials.
-
Rate limiting during probe. The script makes ~80-100 requests concurrently (8 threads). Most APIs tolerate this, but if you hit 429s, increase --timeout or use --no-guess to reduce request count. The script auto-detects rate-limit exhaustion (via X-RateLimit-Remaining: 0) and skips resource guessing to avoid false positives — check the report for rate_limited_skipped_guessing: true.
-
Uniform 403 ≠ auth-gated. If every discovery path returns 403 with the same error body, you're rate-limited, not auth-blocked. Check rate-limit headers (X-RateLimit-Remaining) before interpreting 403s as auth requirements. A true auth-gated API returns 401 (with WWW-Authenticate) or mixed 200/401/403 across paths, not uniform 403.
-
403 on /graphql means the endpoint exists. A 403 on /graphql confirms the endpoint is there but introspection is blocked — not that GraphQL is absent. A 404 means the endpoint doesn't exist. Don't conflate the two.
-
CORS headers only on OPTIONS. Some APIs only send CORS headers on OPTIONS preflight, not on GET. The probe checks OPTIONS separately for this reason.
-
False positives on /health, /status. These paths may return 200 from a load balancer or CDN even if the API itself is down. Treat them as "something is listening" not "API is healthy".
-
Server fingerprint unreliable. X-Powered-By is often stripped by proxies. The framework hints are best-effort — don't treat them as definitive.
-
Self-signed certs. The script uses urllib which verifies certs by default. PYTHONHTTPSVERIFY=0 does NOT affect urllib (it only affects requests/httpx). Self-signed targets are currently unsupported — use --allow-private for internal hosts but self-signed TLS requires a future --insecure flag with a custom ssl.SSLContext.
-
Concurrency is load-bearing. The probe script uses concurrent.futures.ThreadPoolExecutor(max_workers=8) for both well-known paths and resource guessing. Sequential probing of ~90 paths times out against slow APIs (>120s). If you modify the script, preserve the thread pool — do not revert to sequential requests.
-
SPA catch-all false positives. Next.js, Nuxt, and other SPA frameworks serve the same HTML page (200 + text/html) for every path — including /api/users, /health, /openapi.json, etc. This floods the report with bogus "endpoints" that are just the SPA shell. The script auto-detects this in REST resource guessing (uniform 200 + text/html + similar body lengths → discarded) and guards SOAP detection (WSDL path returning text/html is not SOAP). However, the discovery-paths phase may still show many html-docs entries — these are SPA routes, not real API docs. If the server fingerprint shows Next.js/Nuxt and most discovery paths return 200 text/html, treat all but the OpenAPI spec (if found) as SPA noise.
-
MCP stdio server hangs. Some MCP servers don't exit cleanly on stdin close. The probe sends terminate() then waits 5s before kill(). If a server ignores SIGTERM, the probe may take 5s longer. This is expected — the probe always cleans up.
-
Reports redact credential values, not auth protocol. Auth detection still surfaces scheme (Bearer, Basic, WWW-Authenticate, “API key required”, cookie names). Echoed Authorization / X-API-Key values, Set-Cookie values, and token-shaped strings in error bodies are replaced with *** so reports/logs stay shareable. If a header looks “empty” of secrets, check for *** — that means a secret was present and scrubbed.
-
MCP protocol version mismatch. The probe sends protocol version 2025-06-18. Older MCP servers may use 2024-11-05 — the server negotiates and returns its supported version in the response. The probe reports the negotiated version in the summary. If initialize fails with a protocol error, the server may require an older version.
-
MCP HTTP endpoint URL. MCP over HTTP uses a single endpoint URL (e.g. https://mcp.example.com/mcp), not path-based routing. All JSON-RPC requests go to the same URL via POST. Don't append /tools/list or similar — the --mcp flag handles this correctly.
-
MCP resources/prompts may be unsupported. Not all MCP servers implement resources or prompts. The probe gracefully handles method not found errors and reports "None" for unsupported primitives. This is not an error — it means the server only implements tools.
-
MCP StreamableHTTP requires Accept header. Modern MCP servers (protocol 2025-06-18) use the StreamableHTTP transport, which requires Accept: application/json, text/event-stream in request headers. Without it, the server returns 406 Not Acceptable. The probe sends this header automatically.
-
MCP SSE response format. StreamableHTTP servers may respond with text/event-stream (SSE) format — data: {"jsonrpc":"2.0","result":{...}} — instead of plain JSON. The probe auto-detects and extracts JSON from SSE data: lines. If you see "Non-JSON response" errors against an MCP server, the SSE parser may need updating.
-
Credentials exposed via process argv. Tokens passed via --auth, --mcp-env, and --header appear in ps, shell history, and agent logs. For basic auth, --auth must be user:password (not just the password). Prefer reading credentials from environment variables or .env files when possible. The SKILL.md examples use redacted tokens — never use real tokens in examples.
-
SSRF protection. The probe blocks private/internal IP ranges (RFC1918, loopback, link-local, CGNAT 100.64/10, ULA fc00::/7, IPv4-mapped IPv6 like ::ffff:127.0.0.1) and cloud metadata endpoints (169.254.169.254). Uses ipaddress module for accurate detection — no brittle string prefixes. Redirects are disabled to prevent redirect-based SSRF. Use --allow-private to override (e.g. probing localhost for local dev APIs). When probing returns all connection errors and the target is internal, check for the ssrf_warning field in the JSON report.
-
Command allowlist for --mcp-stdio. Only known package runners (npx, uvx, python3, python, node, bunx) are allowed — no arbitrary commands or absolute paths. This prevents prompt-injected "MCP server" strings from executing arbitrary commands.
-
Body size capped at 5 MiB. Response bodies are capped at 5 MiB to prevent memory DoS. Truncated responses include [truncated at 5 MiB] in the body. If an OpenAPI spec or GraphQL schema exceeds this, it will be incomplete.
-
MCP session-ID support. The probe captures Mcp-Session-Id from the initialize response and replays it on all subsequent requests. This is required by sessionful StreamableHTTP servers. Stateless servers (Composio, AlphaVantage) ignore the header.
-
MCP stdio inherits scrubbed environment. The subprocess inherits a scrubbed copy of os.environ: only safe vars (PATH, HOME, USER, LANG, LC_*, TERM, SHELL, XDG_*) are kept; anything containing secret substrings (TOKEN, SECRET, PASSWORD, API_KEY, CREDENTIAL, PRIVATE_KEY, ACCESS_KEY, CLIENT_SECRET) is stripped. Pass secrets explicitly via --mcp-env. macOS works if npx/node are on the inherited PATH — set PATH via --mcp-env if needed.
-
--mcp and --mcp-stdio are mutually exclusive. The CLI rejects both flags together. If you need to try both transports for the same server, run two separate commands.