#!/usr/bin/env python3 """Ediz Yesilkaya public resource CLI 1.0.0; Python 3.9+, standard library only.""" import argparse import json import sys import urllib.error import urllib.request import uuid BASE = 'https://www.edizyesilkaya.com.tr' VERSION = '1.0.0' MAX_RESPONSE = 2 * 1024 * 1024 class ClientError(Exception): pass class NoRedirect(urllib.request.HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): return None OPEN = urllib.request.build_opener(NoRedirect()) def request(path, payload, headers=None, notification=False): req = urllib.request.Request(BASE + path, data=json.dumps(payload, ensure_ascii=False).encode('utf-8'), headers={ 'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'EdizSiteCLI/' + VERSION, **(headers or {})}, method='POST') try: with OPEN.open(req, timeout=20) as response: raw = response.read(MAX_RESPONSE + 1) if len(raw) > MAX_RESPONSE: raise ClientError('Yanıt boyutu sınırı aşıldı.') if notification: if response.status != 202: raise ClientError('MCP bildirimi 202 ile kabul edilmedi.') return None if response.status != 200: raise ClientError('Beklenmeyen HTTP durumu: ' + str(response.status)) if response.headers.get_content_type() != 'application/json': raise ClientError('Sunucu JSON yerine farklı içerik döndürdü.') data = json.loads(raw.decode('utf-8')) except urllib.error.HTTPError as exc: raise ClientError('HTTP ' + str(exc.code) + ': uç nokta veya erişim politikasını kontrol edin.') from exc except (urllib.error.URLError, TimeoutError, OSError) as exc: raise ClientError('Bağlantı kurulamadı veya zaman aşımı oluştu.') from exc except (UnicodeError, ValueError) as exc: raise ClientError('Sunucu geçerli UTF-8 JSON döndürmedi.') from exc if not isinstance(data, dict) or data.get('jsonrpc') != '2.0' or data.get('id') != payload.get('id'): raise ClientError('Geçersiz JSON-RPC yanıtı veya istek kimliği.') if 'error' in data: error = data['error'] if isinstance(error, dict): raise ClientError('JSON-RPC ' + str(error.get('code')) + ': ' + str(error.get('message', 'Bilinmeyen hata'))) raise ClientError('Geçersiz JSON-RPC hata nesnesi.') if not isinstance(data.get('result'), dict): raise ClientError('JSON-RPC result nesnesi bulunamadı.') return data['result'] def topic(value): value = value.strip() if not 1 <= len(value) <= 160 or any(ord(c) < 32 or ord(c) == 127 for c in value): raise ClientError('Konu 1–160 karakter olmalı; kontrol karakteri içermemeli.') return value def mcp(method, params=None): headers = {'MCP-Protocol-Version': '2025-11-25'} init = request('/mcp/', {'jsonrpc': '2.0', 'id': 1, 'method': 'initialize', 'params': { 'protocolVersion': '2025-11-25', 'capabilities': {}, 'clientInfo': {'name': 'ediz-site-cli', 'version': VERSION}}}, headers) version = init.get('protocolVersion') if version not in ('2025-11-25', '2025-06-18', '2025-03-26'): raise ClientError('Sunucu desteklenmeyen bir MCP sürümü seçti.') headers = {'MCP-Protocol-Version': version} request('/mcp/', {'jsonrpc': '2.0', 'method': 'notifications/initialized'}, headers, notification=True) return request('/mcp/', {'jsonrpc': '2.0', 'id': 2, 'method': method, 'params': params or {}}, headers) def search(query): result = mcp('tools/call', {'name': 'build_blog_search_url', 'arguments': {'query': topic(query)}}) if result.get('isError'): raise ClientError('MCP aracı işlemi tamamlayamadı.') try: value = json.loads(result['content'][0]['text']) if not isinstance(value, dict) or not isinstance(value.get('searchUrl'), str): raise ValueError() return value except (KeyError, IndexError, TypeError, ValueError) as exc: raise ClientError('Beklenen blog arama bağlantısı alınamadı.') from exc def resources(): return mcp('resources/list') def read_guide(name): files = {'llms': 'llms.txt', 'agents': 'AGENTS.md'} if name not in files: raise ClientError('Rehber llms veya agents olmalı.') return mcp('resources/read', {'uri': BASE + '/' + files[name]}) def a2a_topic(query): result = request('/a2a/', {'jsonrpc': '2.0', 'id': 1, 'method': 'SendMessage', 'params': { 'message': {'messageId': 'cli-' + uuid.uuid4().hex, 'role': 'ROLE_USER', 'parts': [{'text': topic(query)}]}}}, {'A2A-Version': '1.0'}) message = result.get('message') if not isinstance(message, dict) or message.get('role') != 'ROLE_AGENT' or not isinstance(message.get('parts'), list): raise ClientError('Beklenen A2A mesajı alınamadı.') return result def main(argv=None): parser = argparse.ArgumentParser(description='Ediz Yeşilkaya herkese açık kaynak CLI aracı. Tıbbi cevap üretmez.') parser.add_argument('--version', action='version', version=VERSION) sub = parser.add_subparsers(dest='command', required=True) for name, help_text in [('search', 'Blog arama bağlantısı oluşturur; yazıları okumaz.'), ('a2a', 'A2A üzerinden kaynak bağlantıları ve site rehberi alır.')]: item = sub.add_parser(name, help=help_text) item.add_argument('query', help='1–160 karakterlik kısa konu; kişisel/hasta bilgisi göndermeyin.') sub.add_parser('resources', help='MCP kaynaklarını listeler.') item = sub.add_parser('read', help='Herkese açık rehberi okur.') item.add_argument('guide', choices=['llms', 'agents']) args = parser.parse_args(argv) try: if args.command == 'search': value = search(args.query) elif args.command == 'a2a': value = a2a_topic(args.query) elif args.command == 'resources': value = resources() else: value = read_guide(args.guide) print(json.dumps(value, ensure_ascii=False, indent=2)) return 0 except ClientError as exc: print(json.dumps({'error': str(exc)}, ensure_ascii=False), file=sys.stderr) return 1 if __name__ == '__main__': for stream in (sys.stdout, sys.stderr): if hasattr(stream, 'reconfigure'): stream.reconfigure(encoding='utf-8') sys.exit(main())