Skip to main content
Glama
moma1992

Yaizu Smart City MCP Server

by moma1992
test_direct_login.py13.9 kB
#!/usr/bin/env python3 """ APIカタログサイトへの直接ログインテスト ログインページ経由でフォーム認証を行う """ import asyncio import json import os from pathlib import Path from typing import Dict from urllib.parse import urljoin import aiohttp from bs4 import BeautifulSoup from dotenv import load_dotenv load_dotenv() BASE_URL = "https://city-api-catalog.smartcity-pf.com/yaizu" LOGIN_URL = "https://city-api-catalog.smartcity-pf.com/yaizu/login" EMAIL = os.getenv("YAIZU_API_EMAIL") PASSWORD = os.getenv("YAIZU_API_PASSWORD") class DirectLoginTester: """ログインページ経由での認証テスト""" def __init__(self): self.session = None self.logged_in = False async def __aenter__(self): # クッキーを保持するセッションを作成 self.session = aiohttp.ClientSession( connector=aiohttp.TCPConnector(ssl=False) ) return self async def __aexit__(self, exc_type, exc_val, exc_tb): if self.session: await self.session.close() async def get_login_page(self): """ログインページを取得してフォーム情報を解析""" print("\n" + "="*50) print("ステップ1: ログインページの解析") print("="*50) print(f"URL: {LOGIN_URL}") try: async with self.session.get(LOGIN_URL) as response: print(f"ステータス: {response.status}") if response.status != 200: print("❌ ログインページにアクセスできません") return None html = await response.text() soup = BeautifulSoup(html, 'lxml') # ページタイトル title = soup.find('title') if title: print(f"ページタイトル: {title.get_text(strip=True)}") # フォームを探す forms = soup.find_all('form') print(f"\nフォーム数: {len(forms)}") login_form = None for form in forms: # ログインフォームを特定 inputs = form.find_all('input') has_email = any(inp.get('type') == 'email' or inp.get('name') in ['email', 'username', 'user'] for inp in inputs) has_password = any(inp.get('type') == 'password' or inp.get('name') in ['password', 'pass'] for inp in inputs) if has_email or has_password: login_form = form print("✅ ログインフォーム発見") # フォーム詳細 action = form.get('action', '') method = form.get('method', 'GET').upper() print(f" Action: {action}") print(f" Method: {method}") # 入力フィールド print("\n入力フィールド:") for inp in inputs: input_type = inp.get('type', 'text') input_name = inp.get('name', '') required = 'required' in inp.attrs if input_name: print(f" - {input_name}: type={input_type}, required={required}") # hidden フィールド(CSRFトークンなど) hidden_fields = {} for inp in inputs: if inp.get('type') == 'hidden' and inp.get('name'): hidden_fields[inp['name']] = inp.get('value', '') if hidden_fields: print(f"\nHiddenフィールド: {list(hidden_fields.keys())}") return { 'action': action, 'method': method, 'inputs': [(inp.get('name'), inp.get('type')) for inp in inputs if inp.get('name')], 'hidden_fields': hidden_fields } if not login_form: print("❌ ログインフォームが見つかりません") # デバッグ: すべてのフォームの情報を表示 for i, form in enumerate(forms, 1): print(f"\nフォーム{i}:") print(f" Action: {form.get('action')}") inputs = form.find_all('input') for inp in inputs: print(f" - {inp.get('name')}: {inp.get('type')}") return None except Exception as e: print(f"❌ エラー: {e}") return None async def perform_login(self, form_info: Dict): """フォームを使用してログイン""" print("\n" + "="*50) print("ステップ2: ログイン実行") print("="*50) if not form_info: print("❌ フォーム情報がありません") return False # ログインデータを準備 login_data = { **form_info.get('hidden_fields', {}) } # メール/ユーザー名フィールドを探す for field_name, field_type in form_info['inputs']: if field_name and field_name.lower() in ['email', 'username', 'user', 'login']: login_data[field_name] = EMAIL print(f"📧 {field_name}: {EMAIL}") elif field_name and field_name.lower() in ['password', 'pass', 'pwd']: login_data[field_name] = PASSWORD print(f"🔒 {field_name}: ****") # ログインURLを構築 action = form_info['action'] if not action.startswith('http'): login_endpoint = urljoin(LOGIN_URL, action) if action else LOGIN_URL else: login_endpoint = action print(f"\n📮 送信先: {login_endpoint}") print(f"メソッド: {form_info['method']}") try: # ログインリクエストを送信 if form_info['method'] == 'POST': async with self.session.post( login_endpoint, data=login_data, allow_redirects=True ) as response: print(f"\nレスポンスステータス: {response.status}") print(f"最終URL: {response.url}") # リダイレクトされた場合は成功の可能性が高い if str(response.url) != login_endpoint: print("✅ リダイレクト検出 - ログイン成功の可能性") self.logged_in = True # レスポンスを確認 html = await response.text() soup = BeautifulSoup(html, 'lxml') # エラーメッセージを探す error_elements = soup.find_all(['div', 'span', 'p'], class_=lambda x: x and 'error' in str(x).lower()) if error_elements: print("⚠️ エラーメッセージ検出:") for err in error_elements: print(f" {err.get_text(strip=True)}") # ログイン成功の兆候を探す if any(keyword in html.lower() for keyword in ['logout', 'ログアウト', 'dashboard', 'catalog']): print("✅ ログイン成功を示すキーワード検出") self.logged_in = True return self.logged_in else: # GET method async with self.session.get( login_endpoint, params=login_data, allow_redirects=True ) as response: print(f"\nレスポンスステータス: {response.status}") return response.status == 200 except Exception as e: print(f"❌ ログインエラー: {e}") return False async def check_catalog_access(self): """ログイン後のカタログアクセス確認""" print("\n" + "="*50) print("ステップ3: カタログページへのアクセス確認") print("="*50) catalog_urls = [ f"{BASE_URL}/documentation", f"{BASE_URL}/catalog", f"{BASE_URL}/services", f"{BASE_URL}/dashboard" ] for url in catalog_urls: print(f"\n🔍 アクセステスト: {url}") try: async with self.session.get(url, allow_redirects=False) as response: print(f" ステータス: {response.status}") if response.status == 200: html = await response.text() soup = BeautifulSoup(html, 'lxml') # ページタイトル title = soup.find('title') if title: print(f" タイトル: {title.get_text(strip=True)}") # API関連の要素を探す api_elements = soup.find_all(['div', 'article', 'section'], class_=lambda x: x and any(k in str(x).lower() for k in ['api', 'service', 'catalog'])) if api_elements: print(f" ✅ API要素発見: {len(api_elements)} 個") # 最初の数個を表示 for elem in api_elements[:3]: name = elem.find(['h1', 'h2', 'h3', 'a']) if name: print(f" - {name.get_text(strip=True)[:50]}") # リンクを探す links = soup.find_all('a', href=True) api_links = [link for link in links if any(k in link.get('href', '').lower() or k in link.get_text(strip=True).lower() for k in ['api', 'spec', 'document', 'service'])] if api_links: print(f" 📎 API関連リンク: {len(api_links)} 個") for link in api_links[:5]: text = link.get_text(strip=True) href = link.get('href', '') if text: print(f" - {text[:30]}: {href[:50]}") # 成功したURLでHTMLを保存(デバッグ用) if api_elements or api_links: debug_file = Path(f"debug_{url.split('/')[-1]}.html") with open(debug_file, 'w', encoding='utf-8') as f: f.write(html) print(f" 💾 デバッグ用HTML保存: {debug_file}") return True elif response.status == 302 or response.status == 301: location = response.headers.get('Location', '') print(f" リダイレクト先: {location}") if 'login' in location.lower(): print(f" ⚠️ ログインページへのリダイレクト - 認証が必要") except Exception as e: print(f" ❌ エラー: {e}") return False async def main(): """メイン実行""" print("="*60) print("焼津市APIカタログ - 直接ログインテスト") print("="*60) print(f"ログインURL: {LOGIN_URL}") print(f"ユーザー: {EMAIL}") async with DirectLoginTester() as tester: # 1. ログインページの解析 form_info = await tester.get_login_page() if form_info: # 2. ログイン実行 login_success = await tester.perform_login(form_info) if login_success: # 3. カタログアクセス確認 await tester.check_catalog_access() else: print("\n❌ ログインに失敗しました") else: print("\n❌ ログインフォームの解析に失敗しました") print("\n" + "="*60) print("テスト完了") print("="*60) if __name__ == "__main__": asyncio.run(main())

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/moma1992/smartcity-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server