iflow-mcp_sdi2200262-eclass-mcp-server 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,185 @@
1
+ """Run all tests for the eClass MCP Server."""
2
+
3
+ import asyncio
4
+ import logging
5
+ import os
6
+ import sys
7
+
8
+ from dotenv import load_dotenv
9
+
10
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
11
+ from eclass_mcp_server.server import (
12
+ handle_authstatus,
13
+ handle_get_courses,
14
+ handle_login,
15
+ handle_logout,
16
+ session_state,
17
+ )
18
+
19
+ logging.basicConfig(
20
+ level=logging.INFO,
21
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
22
+ )
23
+ logger = logging.getLogger('run_all_tests')
24
+
25
+ test_results = {
26
+ 'login': False,
27
+ 'authstatus_after_login': False,
28
+ 'get_courses': False,
29
+ 'logout': False,
30
+ 'authstatus_after_logout': False
31
+ }
32
+
33
+
34
+ async def test_login() -> bool:
35
+ """Test the login functionality."""
36
+ print("\n=== Testing Login ===")
37
+
38
+ load_dotenv()
39
+
40
+ username = os.getenv('ECLASS_USERNAME')
41
+ password = os.getenv('ECLASS_PASSWORD')
42
+
43
+ if not username or not password:
44
+ print("ERROR: ECLASS_USERNAME and ECLASS_PASSWORD must be set in the .env file.")
45
+ return False
46
+
47
+ print("Attempting login...")
48
+ response = await handle_login()
49
+
50
+ if not response:
51
+ print("ERROR: Empty response from login handler.")
52
+ return False
53
+
54
+ text_content = response[0]
55
+ print(f"Login response: {text_content.text}")
56
+
57
+ if "Login successful" in text_content.text:
58
+ print("Login test SUCCESS!")
59
+ return True
60
+
61
+ print("Login test FAILED!")
62
+ return False
63
+
64
+
65
+ async def test_authstatus() -> bool:
66
+ """Test the authentication status functionality."""
67
+ print("\n=== Testing Auth Status ===")
68
+
69
+ print("Checking authentication status...")
70
+ response = await handle_authstatus()
71
+
72
+ if not response:
73
+ print("ERROR: Empty response from authstatus handler.")
74
+ return False
75
+
76
+ text_content = response[0]
77
+ print(f"Auth status response: {text_content.text}")
78
+
79
+ if "Status: Logged in as" in text_content.text:
80
+ print("Auth status (logged in) test SUCCESS!")
81
+ return True
82
+ if "Status: Not logged in" in text_content.text and not session_state.logged_in:
83
+ print("Auth status (not logged in) test SUCCESS!")
84
+ return True
85
+ if "Status: Session expired" in text_content.text and not session_state.is_session_valid():
86
+ print("Auth status (session expired) test SUCCESS!")
87
+ return True
88
+
89
+ print("Auth status test FAILED!")
90
+ return False
91
+
92
+
93
+ async def test_get_courses() -> bool:
94
+ """Test the course retrieval functionality."""
95
+ print("\n=== Testing Course Retrieval ===")
96
+
97
+ if not session_state.logged_in:
98
+ print("Not logged in, skipping course retrieval test.")
99
+ return False
100
+
101
+ print("Retrieving courses...")
102
+ response = await handle_get_courses()
103
+
104
+ if not response:
105
+ print("ERROR: Empty response from get_courses handler.")
106
+ return False
107
+
108
+ text_content = response[0]
109
+ print(f"Course retrieval response: {text_content.text}")
110
+
111
+ if "Error" in text_content.text:
112
+ print("Course retrieval test FAILED!")
113
+ return False
114
+
115
+ if "No courses found" in text_content.text:
116
+ print("No courses found, but API worked correctly.")
117
+ print("Course retrieval test SUCCESS!")
118
+ return True
119
+
120
+ if "Found" in text_content.text and "courses" in text_content.text:
121
+ if "URL:" in text_content.text:
122
+ print("Course retrieval test SUCCESS!")
123
+ return True
124
+ print("Course retrieval test FAILED! Output format is incorrect (missing URLs).")
125
+ return False
126
+
127
+ print("Unexpected response from course retrieval.")
128
+ print("Course retrieval test FAILED!")
129
+ return False
130
+
131
+
132
+ async def test_logout() -> bool:
133
+ """Test the logout functionality."""
134
+ print("\n=== Testing Logout ===")
135
+
136
+ if not session_state.logged_in:
137
+ print("Not logged in, skipping logout test.")
138
+ return False
139
+
140
+ print("Attempting logout...")
141
+ response = await handle_logout()
142
+
143
+ if not response:
144
+ print("ERROR: Empty response from logout handler.")
145
+ return False
146
+
147
+ text_content = response[0]
148
+ print(f"Logout response: {text_content.text}")
149
+
150
+ if "Successfully logged out" in text_content.text:
151
+ print("Logout test SUCCESS!")
152
+ return True
153
+
154
+ print("Logout test FAILED!")
155
+ return False
156
+
157
+
158
+ async def run_all_tests() -> None:
159
+ """Run all tests in sequence."""
160
+ print("\n====== Starting All eClass MCP Server Tests ======\n")
161
+
162
+ test_results['login'] = await test_login()
163
+
164
+ if test_results['login']:
165
+ test_results['authstatus_after_login'] = await test_authstatus()
166
+ test_results['get_courses'] = await test_get_courses()
167
+ test_results['logout'] = await test_logout()
168
+
169
+ test_results['authstatus_after_logout'] = await test_authstatus()
170
+
171
+ print("\n====== Test Results Summary ======")
172
+ for test_name, result in test_results.items():
173
+ status = "PASSED" if result else "FAILED"
174
+ print(f"{test_name}: {status}")
175
+
176
+ if all(test_results.values()):
177
+ print("\nAll tests PASSED!")
178
+ else:
179
+ print("\nSome tests FAILED!")
180
+
181
+ print("\n====== Completed All eClass MCP Server Tests ======\n")
182
+
183
+
184
+ if __name__ == "__main__":
185
+ asyncio.run(run_all_tests())
@@ -0,0 +1,92 @@
1
+ """Test course retrieval functionality for eClass MCP Server."""
2
+
3
+ import asyncio
4
+ import logging
5
+ import os
6
+ import sys
7
+
8
+ from dotenv import load_dotenv
9
+
10
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
11
+ from eclass_mcp_server.server import handle_get_courses, handle_login, session_state
12
+
13
+ logging.basicConfig(
14
+ level=logging.INFO,
15
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
16
+ )
17
+ logger = logging.getLogger('test_courses')
18
+
19
+
20
+ async def ensure_login() -> bool:
21
+ """Ensure logged in before testing course retrieval."""
22
+ if session_state.logged_in and session_state.is_session_valid():
23
+ print("Already logged in, proceeding with tests.")
24
+ return True
25
+
26
+ print("Not logged in, attempting login...")
27
+
28
+ load_dotenv()
29
+
30
+ username = os.getenv('ECLASS_USERNAME')
31
+ password = os.getenv('ECLASS_PASSWORD')
32
+
33
+ if not username or not password:
34
+ print("ERROR: ECLASS_USERNAME and ECLASS_PASSWORD must be set in the .env file.")
35
+ return False
36
+
37
+ response = await handle_login()
38
+
39
+ if response and "Login successful" in response[0].text:
40
+ print("Login successful, proceeding with tests.")
41
+ return True
42
+
43
+ print("Login failed, cannot proceed with course tests.")
44
+ return False
45
+
46
+
47
+ async def test_get_courses() -> bool:
48
+ """Test the course retrieval functionality."""
49
+ print("Testing course retrieval functionality...")
50
+
51
+ response = await handle_get_courses()
52
+
53
+ if not response:
54
+ print("ERROR: Empty response from get_courses handler.")
55
+ return False
56
+
57
+ text_content = response[0]
58
+ print(f"Course retrieval response: {text_content.text}")
59
+
60
+ if "Error" in text_content.text:
61
+ print("Course retrieval test FAILED!")
62
+ return False
63
+
64
+ if "No courses found" in text_content.text:
65
+ print("No courses found, but API worked correctly.")
66
+ print("Course retrieval test SUCCESS!")
67
+ return True
68
+
69
+ if "Found" in text_content.text and "courses" in text_content.text:
70
+ if "URL:" in text_content.text:
71
+ print("Course retrieval test SUCCESS!")
72
+ return True
73
+ print("Course retrieval test FAILED! Output format is incorrect (missing URLs).")
74
+ return False
75
+
76
+ print("Unexpected response from course retrieval.")
77
+ print("Course retrieval test FAILED!")
78
+ return False
79
+
80
+
81
+ async def main() -> None:
82
+ """Run course tests."""
83
+ print("=== Starting eClass MCP Server Course Tests ===")
84
+
85
+ if await ensure_login():
86
+ await test_get_courses()
87
+
88
+ print("=== Completed eClass MCP Server Course Tests ===")
89
+
90
+
91
+ if __name__ == "__main__":
92
+ asyncio.run(main())
@@ -0,0 +1,87 @@
1
+ """Test login functionality for eClass MCP Server."""
2
+
3
+ import asyncio
4
+ import logging
5
+ import os
6
+ import sys
7
+
8
+ from dotenv import load_dotenv
9
+
10
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
11
+ from eclass_mcp_server.server import handle_login, handle_logout, session_state
12
+
13
+ logging.basicConfig(
14
+ level=logging.INFO,
15
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
16
+ )
17
+ logger = logging.getLogger('test_login')
18
+
19
+
20
+ async def test_login() -> bool:
21
+ """Test the login functionality."""
22
+ print("Testing login functionality...")
23
+
24
+ load_dotenv()
25
+
26
+ username = os.getenv('ECLASS_USERNAME')
27
+ password = os.getenv('ECLASS_PASSWORD')
28
+
29
+ if not username or not password:
30
+ print("ERROR: ECLASS_USERNAME and ECLASS_PASSWORD must be set in the .env file.")
31
+ return False
32
+
33
+ response = await handle_login()
34
+
35
+ if not response:
36
+ print("ERROR: Empty response from login handler.")
37
+ return False
38
+
39
+ text_content = response[0]
40
+ print(f"Login response: {text_content.text}")
41
+
42
+ if "Login successful" in text_content.text:
43
+ print("Login test SUCCESS!")
44
+ return True
45
+
46
+ print("Login test FAILED!")
47
+ return False
48
+
49
+
50
+ async def test_logout() -> bool:
51
+ """Test the logout functionality."""
52
+ if not session_state.logged_in:
53
+ print("Not logged in, skipping logout test.")
54
+ return False
55
+
56
+ print("Testing logout functionality...")
57
+
58
+ response = await handle_logout()
59
+
60
+ if not response:
61
+ print("ERROR: Empty response from logout handler.")
62
+ return False
63
+
64
+ text_content = response[0]
65
+ print(f"Logout response: {text_content.text}")
66
+
67
+ if "Successfully logged out" in text_content.text:
68
+ print("Logout test SUCCESS!")
69
+ return True
70
+
71
+ print("Logout test FAILED!")
72
+ return False
73
+
74
+
75
+ async def main() -> None:
76
+ """Run login tests."""
77
+ print("=== Starting eClass MCP Server Login Tests ===")
78
+
79
+ login_success = await test_login()
80
+ if login_success:
81
+ await test_logout()
82
+
83
+ print("=== Completed eClass MCP Server Login Tests ===")
84
+
85
+
86
+ if __name__ == "__main__":
87
+ asyncio.run(main())
@@ -0,0 +1,158 @@
1
+ Metadata-Version: 2.4
2
+ Name: iflow-mcp_sdi2200262-eclass-mcp-server
3
+ Version: 0.1.0
4
+ Summary: An MCP server for the Open eClass platform by GUnet
5
+ Author-email: CobuterMan <haidemenoss@gmail.com>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: beautifulsoup4>=4.12.3
9
+ Requires-Dist: mcp>=1.24.0
10
+ Requires-Dist: python-dotenv>=1.0.1
11
+ Requires-Dist: requests>=2.32.0
12
+ Description-Content-Type: text/markdown
13
+
14
+ # eClass MCP Server
15
+
16
+ An MCP server for interacting with the [Open eClass](https://github.com/gunet/openeclass) platform, with support for UoA's SSO authentication.
17
+
18
+ <p align="center">
19
+ <a href="https://github.com/modelcontextprotocol/python-sdk"><img src="https://img.shields.io/badge/MCP-Protocol-blue" alt="MCP Protocol"></a>
20
+ <a href="https://www.gnu.org/licenses/gpl-3.0"><img src="https://img.shields.io/badge/License-GPLv3-blue.svg" alt="License: GPL v3"></a>
21
+ <img src="https://img.shields.io/badge/Python-3.10%2B-blue" alt="Python: 3.10+">
22
+ </p>
23
+
24
+ <p align="center">
25
+ <img src="assets/example.png" alt="Example Usage" width="600">
26
+ </p>
27
+
28
+ ## Features
29
+
30
+ - **SSO Authentication**: Log in through UoA's CAS SSO system
31
+ - **Course Retrieval**: Get list of enrolled courses
32
+ - **Session Management**: Persistent sessions between tool calls
33
+ - **Status Checking**: Verify authentication status
34
+
35
+ ## Quick Start
36
+
37
+ ### Prerequisites
38
+
39
+ - Python 3.10+
40
+ - [uv](https://github.com/astral-sh/uv) (recommended) or pip
41
+
42
+ ### Installation
43
+
44
+ ```bash
45
+ git clone https://github.com/sdi2200262/eclass-mcp-server.git
46
+ cd eclass-mcp-server
47
+ uv sync --dev --all-extras
48
+ ```
49
+
50
+ ### Configuration
51
+
52
+ Create a `.env` file (or copy `example.env`):
53
+
54
+ ```bash
55
+ ECLASS_USERNAME=your_username
56
+ ECLASS_PASSWORD=your_password
57
+ ```
58
+
59
+ Optional settings:
60
+ ```bash
61
+ ECLASS_URL=https://eclass.uoa.gr # Default
62
+ ECLASS_SSO_DOMAIN=sso.uoa.gr # Default
63
+ ECLASS_SSO_PROTOCOL=https # Default
64
+ ```
65
+
66
+ ### Running
67
+
68
+ ```bash
69
+ # Using the entry point script
70
+ python run_server.py
71
+
72
+ # Or as a module
73
+ python -m src.eclass_mcp_server.server
74
+ ```
75
+
76
+ ## MCP Client Configuration
77
+
78
+ To use this MCP server with Claude Desktop, VS Code, Cursor, or any MCP-compatible client, configure your client to run:
79
+
80
+ ```bash
81
+ python3 /absolute/path/to/eclass-mcp-server/run_server.py
82
+ ```
83
+
84
+ Set the following environment variables in your client's MCP configuration:
85
+
86
+ ```json
87
+ {
88
+ "env": {
89
+ "ECLASS_USERNAME": "your_username",
90
+ "ECLASS_PASSWORD": "your_password"
91
+ }
92
+ }
93
+ ```
94
+
95
+ **Optional environment variables:**
96
+ - `ECLASS_URL` - OpenEclass instance URL (default: `https://eclass.uoa.gr`)
97
+ - `ECLASS_SSO_DOMAIN` - SSO domain (default: `sso.uoa.gr`)
98
+ - `ECLASS_SSO_PROTOCOL` - SSO protocol (default: `https`)
99
+
100
+ Refer to your specific client's documentation for how to add MCP servers to your configuration.
101
+
102
+ ## Available Tools
103
+
104
+ | Tool | Description |
105
+ |------|-------------|
106
+ | `login` | Authenticate using credentials from `.env` |
107
+ | `get_courses` | Retrieve enrolled courses (requires login) |
108
+ | `logout` | End the current session |
109
+ | `authstatus` | Check authentication status |
110
+
111
+ All tools use a dummy `random_string` parameter (MCP protocol requirement).
112
+
113
+ ## Standalone Client
114
+
115
+ For non-MCP usage, a standalone client is included:
116
+
117
+ ```bash
118
+ python eclass_client.py
119
+ ```
120
+
121
+ This demonstrates the core functionality without MCP integration. See [docs/architecture.md](docs/architecture.md) for details.
122
+
123
+ ## Documentation
124
+
125
+ - [Architecture](docs/architecture.md) - System design and authentication flow
126
+ - [Wire Protocol](docs/reference/wire-protocol.md) - JSON-RPC message formats
127
+ - [Tools Reference](docs/reference/tools-reference.md) - Detailed tool documentation
128
+
129
+ ## Project Structure
130
+
131
+ ```
132
+ eclass-mcp-server/
133
+ ├── run_server.py # Entry point
134
+ ├── eclass_client.py # Standalone client (non-MCP)
135
+ ├── src/eclass_mcp_server/ # Main package
136
+ │ ├── server.py # MCP server and tool handlers
137
+ │ ├── authentication.py # SSO authentication
138
+ │ ├── course_management.py # Course operations
139
+ │ ├── html_parsing.py # HTML parsing utilities
140
+ │ └── test/ # Test scripts
141
+ └── docs/ # Documentation
142
+ ```
143
+
144
+ ## Security
145
+
146
+ - Credentials are stored locally in `.env` only
147
+ - Never passed as tool parameters (preventing AI provider exposure)
148
+ - Sessions maintained in-memory only
149
+ - No cloud services or remote storage
150
+
151
+ ## License
152
+
153
+ [GNU GPL v3.0](LICENSE) - This ensures transparency in credential handling.
154
+
155
+ ## Acknowledgments
156
+
157
+ - [GUnet](https://github.com/gunet) for the [Open eClass platform](https://github.com/gunet/openeclass)
158
+ - This project is an independent interface, not affiliated with GUnet
@@ -0,0 +1,14 @@
1
+ eclass_mcp_server/__init__.py,sha256=dm04z7pStZAy-J_t3dpDxnoNzD0x0RV87GRqbkb3hPw,233
2
+ eclass_mcp_server/authentication.py,sha256=8Ltuba4qG4yiblwK44OboZ8-6R4--E3HpRQ79KYxj_M,6903
3
+ eclass_mcp_server/course_management.py,sha256=8eKkB55O3rJ58IKeycFQACXa0UkP9mh7UIOImMmpfTc,2517
4
+ eclass_mcp_server/html_parsing.py,sha256=ztmiVWqG3Dz_UVhZcBOaGexrJNPPFRY3kSbvNBDu_UY,5303
5
+ eclass_mcp_server/server.py,sha256=J_euIPOCgo5yml8EGRB4-uHXtnoFl7zzyVZOx7frOXc,8117
6
+ eclass_mcp_server/test/__init__.py,sha256=6CWwwhfzisuSlkknsm8jKnhCPTkw68816RmL9EbiYUk,42
7
+ eclass_mcp_server/test/run_all_tests.py,sha256=mT_fqSjhgEUmb1MAxPEi-ZyNwYTErxcRgRGVvM1C8_M,5354
8
+ eclass_mcp_server/test/test_courses.py,sha256=AYv6Tt_ZE1XRbX73L8rYC53rTcNJcoR4b_5Mk7diQOc,2734
9
+ eclass_mcp_server/test/test_login.py,sha256=3wRstWBLe7c5HInDk4wrz3jDBMXFu5hpbz3UAzwP2wo,2218
10
+ iflow_mcp_sdi2200262_eclass_mcp_server-0.1.0.dist-info/METADATA,sha256=iVXJ5fd1h19_iGOWMXOUTs_p6bBJH8C2-SNrxsEgHBA,4694
11
+ iflow_mcp_sdi2200262_eclass_mcp_server-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
12
+ iflow_mcp_sdi2200262_eclass_mcp_server-0.1.0.dist-info/entry_points.txt,sha256=FVYwA1FQMTcR3NcwQECbhKwt6DsUd5ZbYte4YYbUf70,82
13
+ iflow_mcp_sdi2200262_eclass_mcp_server-0.1.0.dist-info/licenses/LICENSE,sha256=BOrRwhm7skCATXYP84QJwMnxb2_hEb61cn7XyrRAUQ4,33188
14
+ iflow_mcp_sdi2200262_eclass_mcp_server-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ iflow-mcp_sdi2200262-eclass-mcp-server = eclass_mcp_server:main