#!/usr/bin/env python3 """Read-only hosted MCP entry probe. Never supplies credentials or prints bodies. A PASS covers the L1 native-bearer resource and challenge only, not login or CRM reads. """ from __future__ import annotations import argparse import json import urllib.error import urllib.parse import urllib.request from datetime import datetime, timezone LIMIT = 65536 class NoRedirect(urllib.request.HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): return None def origin(value: str) -> str: parsed = urllib.parse.urlsplit(value) if (parsed.scheme != 'https' or not parsed.hostname or parsed.username or parsed.password or parsed.path not in ('', '/') or parsed.query or parsed.fragment): raise ValueError('expected an HTTPS origin without credentials, path, query or fragment') # Force malformed ports to fail before any network request. parsed.port return value.rstrip('/') def credential_free_opener(): # No ambient proxy settings or proxy credentials enter this diagnostic. return urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect()) def probe(base: str, *, opener=None) -> dict: base = origin(base) if opener is None: opener = credential_free_opener() checks = [] paths = ('/.well-known/oauth-protected-resource', '/mcp') for path in paths: rpc = path == '/mcp' request = urllib.request.Request( base + path, data=json.dumps({'jsonrpc': '2.0', 'id': 1, 'method': 'initialize', 'params': {'protocolVersion': '2025-11-25', 'capabilities': {}, 'clientInfo': {'name': 'mcp-entry-probe', 'version': '1'}}}).encode() if rpc else None, headers={'Accept': 'application/json, text/event-stream', 'Content-Type': 'application/json'}, ) check = {'path': path, 'status': None, 'passed': False} try: try: response = opener.open(request, timeout=10) except urllib.error.HTTPError as exc: response = exc with response: check['status'] = response.status if response.geturl() != request.full_url: check['reason'] = 'redirected' elif rpc: challenge = response.headers.get('WWW-Authenticate', '') expected = 'Bearer realm="mcp"' # URL/audience values are case-sensitive, unlike the Bearer scheme. check['passed'] = (response.status == 401 and challenge[:6].lower() == 'bearer' and challenge[6:] == expected[6:]) if not check['passed']: check['reason'] = 'expected L1 native-bearer 401' elif response.status != 200: check['reason'] = 'expected discovery HTTP 200' else: raw = response.read(LIMIT + 1) if len(raw) > LIMIT: raise ValueError('oversized document') document = json.loads(raw) if not isinstance(document, dict): raise ValueError('invalid document') check['passed'] = (document.get('resource') == base + '/mcp' and document.get('bearer_methods_supported') == ['header']) if not check['passed']: check['reason'] = 'discovery contract mismatch' except (urllib.error.URLError, TimeoutError, OSError): check['reason'] = 'transport failure' except (ValueError, UnicodeError): check['reason'] = 'invalid discovery JSON' checks.append(check) return {'checkedAt': datetime.now(timezone.utc).isoformat(), 'origin': base, 'entryPassed': all(c['passed'] for c in checks), 'authenticatedRead': 'UNVERIFIED', 'authenticatedWrite': 'UNVERIFIED', 'checks': checks} def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--origin', required=True, type=origin) args = parser.parse_args() result = probe(args.origin) print(json.dumps(result, indent=2)) return 0 if result['entryPassed'] else 1 if __name__ == '__main__': raise SystemExit(main())