Skip to main content
Glama
README.md
# plato-mcp

[![License: AGPL v3](https://img.shields.io/badge/license-AGPL--3.0-blue)](https://www.gnu.org/licenses/agpl-3.0.html)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/)
[![Status: WIP](https://img.shields.io/badge/status-work%20in%20progress-orange)](./PLAN.md)
[![Unofficial](https://img.shields.io/badge/PNU-unofficial%20project-red)](#면책조항)

부산대학교(PNU)의 무들(Moodle) 기반 LMS인 **PLATO**(https://plato.pusan.ac.kr/)를 위한 비공식
[MCP](https://modelcontextprotocol.io/)(Model Context Protocol) 서버입니다. Claude 같은 AI
어시스턴트가 여러분을 대신해 PLATO에 로그인하지 않고도, 여러분이 이미 접근 권한을 가진
강좌 자료·공지·과제·성적·Q&A를 조회·작성·다운로드할 수 있게 해 줍니다.

> **아직 개발 중입니다.** 전체 구현 계획은 [`PLAN.md`](./PLAN.md)를, 현재 진행 상황은
> [GitHub Issues](https://github.com/jin-1119/plato-mcp/issues) /
> [Project 보드](https://github.com/users/jin-1119/projects/1)를 참고하세요.

> **이 서버는 새로운 권한을 부여하지 않습니다.** 여러분의 PLATO 학번·비밀번호로 인증하며,
> 브라우저로 직접 열람할 때 볼 수 있는 것만 볼 수 있습니다 — 다만 그 내용이 AI와의 대화로
> 흘러 들어간다는 차이가 있습니다. 시작하기 전에 아래 [면책조항](#면책조항)과
> [개인정보 안내](#개인정보-안내)를 반드시 읽어 주세요.

---

## 시작하기

이 프로젝트는 두 부류의 독자를 대상으로 합니다 — **그냥 써보고 싶은 일반 사용자**와
**코드를 고치거나 기여하고 싶은 개발자**. 아래에서 본인에게 맞는 쪽을 고르세요.

### 일반 사용자 — 그냥 써보고 싶다면

코드를 내려받거나 서버를 직접 실행할 필요가 없습니다. 이 서버는 공식
[MCP Registry](https://registry.modelcontextprotocol.io)에
`io.github.jin-1119/plato-mcp`로 등록되어 있고, 실제 주소는
`https://plato-mcp.onrender.com/mcp`(Streamable HTTP)입니다.

> Smithery 마켓플레이스는 더 이상 쓰지 않습니다 — Smithery가 신규 외부 서버
> 등록을 막고 2026-10-31에 전체 서비스를 종료하면서, 공식 레지스트리로
> 옮겼습니다. 자세한 배경은 [`docs/smithery_deployment_model.md`](./docs/smithery_deployment_model.md)를
> 참고하세요.

1. 사용 중인 MCP 클라이언트에서 아래 주소를 **원격(remote) MCP 서버**로
   추가합니다:
   - **Claude.ai / Claude Desktop**: 설정 → Connectors → Add custom
     connector → URL에 `https://plato-mcp.onrender.com/mcp` 입력.
   - **Claude Code**: `claude mcp add --transport http plato-mcp https://plato-mcp.onrender.com/mcp`
   - MCP Registry를 직접 검색할 수 있는 클라이언트라면 `io.github.jin-1119/plato-mcp`로
     찾아도 됩니다.
2. 연결 시 PLATO 학번과 비밀번호를 (클라이언트가 지원하는 경우) 커넥터 설정
   값으로, 아니면 헤더/쿼리 파라미터(`pnu_id`, `pnu_password`)로 입력합니다.
   이 값은 **서버 디스크에 저장되지 않으며**, 요청을 처리하는 동안만 메모리에
   존재합니다 — 자세한 내용은 [`SECURITY.md`](./SECURITY.md)를 확인하세요.
3. 연결되면 AI 어시스턴트에게 자연어로 요청하면 됩니다. 예:
   - "이번 주에 제출해야 할 과제가 뭐가 있어?"
   - "이번 학기 성적 알려줘"
   - "OO 과목 3주차 강의자료 다운로드해줘"
   - "OO 과목 공지사항 최근 것부터 보여줘"

> **시작하기 전에 반드시 아래 [면책조항](#면책조항)과
> [개인정보 안내](#개인정보-안내)를 읽어 주세요.** 특히 과제 제출·Q&A 등록처럼
> 되돌릴 수 없는 동작(✏️ 표시)은 실행 전 미리보기 확인을 거치도록 되어 있습니다.
>
> 직접 배포해 보고 싶다면
> [`docs/smithery_publish_guide.md`](./docs/smithery_publish_guide.md)의
> 비전공자용 단계별 가이드를 참고하세요.

### 개발자 — 코드를 고치거나 기여하고 싶다면

버그 수정, 새 도구 추가, 문서 개선 등 기여를 환영합니다. 이슈/브랜치/PR을 이
저장소가 실제로 어떻게 운영하는지는 [`CONTRIBUTING.md`](./CONTRIBUTING.md)에
정리되어 있습니다 — PR을 보내기 전에 먼저 읽어 주세요. 로컬 개발 환경 설정은
바로 아래 [개발 환경 설정](#개발-환경-설정-기여자용) 섹션을 참고하세요.

---

## 제공하는 도구 (MCP Tools)

공식 Moodle webservice API로 동작하는 도구와, `ubboard`(공지/Q&A) HTML을 본인 세션으로
스크레이핑하는 도구로 나뉩니다. 쓰기 도구(✏️)는 실행 전 확인 절차를 거칩니다
(자세한 내용은 [`docs/write_confirmation_pattern.md`](./docs/write_confirmation_pattern.md)).
PLATO가 실제로 제공하는 439개 Moodle API 함수 전체를 이 MCP가 어떻게 다뤘는지(구현/중복/
비활성화/권한없음/미구현 사유)는 [`docs/moodle_api_coverage.md`](./docs/moodle_api_coverage.md)
참고.

**강좌 / 과제 / 성적 / 일정 / 쪽지** — 공식 API

- `list_courses` — 수강 중인 강좌 목록
- `get_course_contents` — 강좌 콘텐츠(주차별 자료 등)
- `get_course_updates_since` — 강좌 변경사항 감지 (표준 Moodle 모듈만 — ubfile/ubboard/vod는
  변경감지 콜백 미구현, 포함 안 됨)
- `get_course_module` — cmid로 모듈 직접 조회
- `search_courses` — PLATO 전체 강좌 카탈로그 검색 (수강 중인 강좌만이 아님)
- `get_recent_courses` — 최근 접속한 강좌 목록
- `get_recent_items` — 최근 열어본 강의자료/활동 목록 (강좌 단위가 아닌 모듈 단위)
- `get_starred_courses` — 즐겨찾기한 강좌 목록
- `list_video_conferences` — 화상강의(BigBlueButton) 조회 (참여 기능은 미지원, 실사용
  사례 없는 forward-looking 기능)
- `list_lessons` — Lesson 활동 조회 (응시 기능은 미지원, 실사용 사례 없음)
- `list_workshops` — Workshop(상호평가) 활동 조회 (제출/평가 기능은 미지원, 실사용 사례 없음)
- `list_feedbacks` — Feedback(설문) 활동 조회 (응답 기능은 미지원, 실사용 사례 없음)
- `list_choices` / `list_surveys` — Choice(투표)/Survey 활동 조회 (응답 기능은 미지원,
  실사용 사례 없음)
- `list_scorms` / `list_h5p_activities` — SCORM/H5P 패키지 콘텐츠 조회 (실행/추적 기능은
  미지원, 실사용 사례 없음)
- `list_glossaries` — Glossary(용어집) 조회 (항목 읽기/쓰기는 미지원, 실사용 사례 없음)
- `list_database_activities` — Database 활동 조회 (항목 읽기/쓰기는 미지원, 실사용 사례 없음)
- `list_wikis` / `list_chats` — Wiki/실시간 채팅 활동 조회 (읽기/쓰기는 미지원, 실사용
  사례 없음)
- `get_user_profile` — 사용자 프로필 조회 (같은 강좌 수강생만, 전체 검색 아님)
- `get_private_files_info` / `add_private_file` ✏️ — 개인파일 영역 조회/업로드 (⚠️ 삭제
  도구 없음, 실제 삭제는 PLATO 웹에서)
- `get_user_preferences` — 사용자 환경설정 조회 (대부분 내부 UI 상태값)
- `get_my_course_groups` — 강좌 내 소속 그룹 조회
- `get_enrolled_users` — 강좌 수강생 명단 조회 (PLATO 참가자 목록과 동일 정보)
- `list_assignments` — 과제 목록
- `get_assignment_detail` — 과제 상세
- `submit_assignment` ✏️ — 과제 제출
- `remove_submission` ✏️ — 과제 제출 철회 (⚠️ 실제 실행 경로는 아직 PLATO에서
  `invalidparameter` 오류 발생, 미리보기만 동작 확인됨)
- `get_grades` — 성적 조회
- `get_grades_access_info` — 이 계정의 성적 조회 권한 확인 (전체 열람 권한 등)
- `list_calendar_events` — 캘린더 일정
- `get_calendar_access_info` — 캘린더 관리 권한 확인
- `create_calendar_event` / `delete_calendar_event` ✏️ — 개인 캘린더 일정 생성/삭제
  (본인 소유 개인 일정만 가능, 강좌/그룹 일정은 불가)
- `get_unread_messages` / `get_unread_notification_count` — 안 읽은 쪽지 (⚠️ PLATO는 1:1
  메시징/대화 기능이 사이트 전체에서 비활성화되어 있음 — 알림만 동작)
- `get_course_completion_status` — 강좌 단위 이수 현황 (이수 기준이 설정된 강좌만)
- `get_activity_completion_status` — 강좌 내 활동/자료별 이수 현황 (동영상 강의 시청률 포함)
- `get_upcoming_action_events` — 마감 임박한 할 일(과제/시청 기한 등) 통합 조회
- `get_courses_by_timeline` — 진행 중/예정/종료 강좌 필터링
- `get_grades_overview` — 전체 강좌 종합 성적 한 번에 조회
- `list_quizzes` / `get_quiz_attempts` / `get_quiz_best_grade` — 퀴즈 조회
- `get_quiz_access_information` / `get_quiz_required_qtypes` / `get_quiz_feedback_for_grade` /
  `get_quiz_combined_review_options` — 퀴즈 응시 전 확인용 조회 (⚠️ 응답 형태 미검증 — 실제
  퀴즈가 있는 강좌가 없어서 파라미터만 라이브로 확인됨)
- `start_quiz_attempt` / `get_quiz_attempt_data` / `save_quiz_attempt_responses` /
  `process_quiz_attempt` / `get_quiz_attempt_summary` / `get_quiz_attempt_review` ✏️ — 퀴즈
  응시 흐름 (⚠️ 전체 미검증, 실제 퀴즈로 끝까지 테스트된 적 없음 — 사용 전 반드시 재검증 권장)
- `list_forums` / `get_forum_discussions` / `get_discussion_posts` — 표준 Moodle 포럼 조회
  (PLATO 자체 공지/Q&A인 `ubboard`와는 다른 모듈)
- `add_discussion` / `add_discussion_post` ✏️ — 포럼 글쓰기/답글

**공지사항 / Q&A(`ubboard`)** — 로그인 세션 스크레이핑

- `list_notices` — 공지사항 목록
- `get_notice_detail` — 공지사항 상세
- `list_qna` — Q&A 게시글 목록
- `get_qna_detail` — Q&A 게시글 상세
- `post_qna_question` ✏️ — Q&A 질문 등록

**파일**

- `download_course_file` — 강좌 첨부파일 다운로드 (원격 실행 시 링크 반환, 위
  [개인정보 안내](#개인정보-안내) 참고)

## 아키텍처

```
src/plato_mcp/
├── server.py            # FastMCP 앱, tool 등록 진입점
├── config.py            # Smithery config 스키마 (pnu_id, pnu_password, ...)
├── auth.py              # 세션별 wstoken + requests.Session 쿠키 in-memory 캐시
├── moodle_client.py     # webservice/rest/server.php 범용 REST 호출 래퍼
├── ubboard/
│   ├── scraper.py       # list_notices/get_notice_detail/list_qna/get_qna_detail
│   ├── writer.py        # post_qna_question (sesskey 처리)
│   └── parser.py        # BeautifulSoup 파싱
├── files.py             # download_course_file
├── tools/                # MCP tool 등록 (courses/assignments/grades/calendar/messages/notices_qna/downloads)
├── models.py              # pydantic 응답 스키마
├── errors.py               # 타입 예외 (AuthError, MoodleAPIError, ScrapeError, RateLimitError)
└── security.py              # 로그/자격증명 마스킹, rate limiting
```

인증 흐름: 세션 시작 시 `login/token.php`로 wstoken을 발급받아 메모리에 캐시합니다.
`ubboard`(공지/Q&A) 접근 시에만 추가로 쿠키 로그인을 수행해 `sesskey`를 스크레이핑합니다.
어떤 경우도 디스크에 저장하지 않습니다. 배경이 된 사전 조사는
[`PLATO_MCP_사전조사.md`](./PLATO_MCP_사전조사.md), 전체 로드맵은
[`PLAN.md`](./PLAN.md)를 참고하세요.

---

## 개발 환경 설정 (기여자용)

> 아래는 이 프로젝트의 **코드를 수정/기여하려는 개발자용** 명령어입니다. PLATO를
> 그냥 써보고 싶은 일반 사용자는 몰라도 되는 내용이며, 실행해도 PLATO 계정에 연결되지
> 않습니다 — 코드와 테스트 도구(`pytest`, `ruff` 등)를 내 컴퓨터에 설치할 뿐입니다.

**사전 요건**

- Python 3.11+
- 로컬 개발/테스트에는 실제 PLATO 계정이 필요 없습니다 (테스트는 mock 기반)

```bash
pip install -e ".[dev]"
pytest
```

## 면책조항

- **부산대학교(PNU) 또는 PLATO/무들 플랫폼과 아무런 제휴 관계가 없으며, 이들의 승인을
  받지 않았습니다.** 이 프로젝트는 여러분이 이미 브라우저로 하고 있는 것과 동일한 PLATO
  계정 접근을 자동화하는 독립적인 커뮤니티 프로젝트입니다 — 새로운 권한을 부여하지 않으며
  PNU를 대신해 어떠한 행위도 하지 않습니다.
- **모든 사용은 본인 책임이며, PNU의 이용약관을 준수해야 합니다.** 특히 공지사항/Q&A
  게시판(`ubboard`)은 공식 API가 없어 로그인된 본인 세션으로 렌더링된 HTML을 직접
  스크레이핑하는데, 이 사이트의 `robots.txt`는 `Disallow: /`를 명시하고 있습니다. 이
  지침은 익명 크롤러를 겨냥한 것이며 인증된 사용자 본인의 열람 행위를 겨냥한 것은
  아니지만, 그래도 스크레이핑인 것은 사실입니다. 이 서버를 계정에 연결하기 전에 본인
  계정에 대한 PNU의 acceptable-use 정책에 부합하는지 확인할 책임은 사용자에게 있습니다.
  권장 사용 방식은 개인적·저빈도 사용이며, 사이트 전체를 대상으로 한 자동 대량
  스크레이핑은 권장하지 않습니다.
- **PLATO 로그인 정보는 이 서버에 의해 디스크에 영구 저장되지 않습니다.** 무엇이,
  얼마나 오래 저장/보관되는지 정확한 내용은 [`SECURITY.md`](./SECURITY.md)를
  참고하세요.
- **이 프로젝트는 제3자에 의한 독립적인 보안 감사를 받지 않았습니다.** 개발 과정에서
  자체적으로 여러 자격증명 처리·오남용 방지 이슈를 발견하고 수정했습니다
  (`docs/security_audit.md`, `docs/abuse_prevention_review.md` 참고) — 하지만
  스스로 찾아 고친 것은 외부 감사와 같은 수준의 보증이 아닙니다.

## 개인정보 안내

이 서버는 강좌 콘텐츠를 그대로 전달합니다 — Q&A 게시판의 경우 **다른 학생들의 이름과
게시글**도 포함됩니다. 이는 본인 계정이 PLATO를 통해 이미 정당하게 접근할 수 있는
내용이며, 이 도구가 새로운 접근 권한을 부여하는 것은 아닙니다. 브라우저로 PLATO를
직접 열람할 때와 다른 점은, 이 내용이 AI 어시스턴트와 나누는 대화의 일부가 된다는
것입니다. 즉 도구 출력으로서 해당 AI/LLM 제공업체(예: Anthropic)에 전송됩니다.
강좌 Q&A 게시판을 대상으로 폭넓은 질문을 하기 전에 이 점을 유념하세요.

전체 검토 내용과 알려진 미해결 사항(PLATO가 Q&A 게시글의 "비공개/강사 전용" 플래그를
서버 측에서 실제로 강제하는지 — 즉 이 도구가 그런 게시글을 애초에 볼 수 없는지 —
여부는 아직 실증적으로 검증되지 않았습니다)은
[`docs/pii_review.md`](./docs/pii_review.md)에서 확인할 수 있습니다.

**원격으로 실행할 경우** (로컬 Claude Code/Desktop이 아니라 Claude.ai 커넥터 등을
통해 실행하는 경우), 인라인 전송 크기 제한을 초과하는 큰 파일에 대해
`download_course_file`은 파일 자체가 아니라 다운로드 *링크*를 반환하며, 이 링크에는
살아있는 PLATO 액세스 토큰이 포함되어 있습니다. **이 토큰은 해당 파일 하나에만
한정되지 않습니다** — 실제로 확인해 본 결과 본인 계정에 대한 임의의 PLATO API 호출을
인증하는 데 사용할 수 있었습니다. 이 링크나 이 링크가 포함된 대화 기록을 공유하는
것은 파일 하나를 공유하는 것이 아니라 계정에 대한 활성 자격증명을 공유하는 것과
같습니다. 자세한 내용은 [`SECURITY.md`](./SECURITY.md)와
[`docs/smithery_deployment_model.md`](./docs/smithery_deployment_model.md)를
참고하세요.


- Lint: `ruff check .`
- 이슈/보드 운영 방식은 [`PLAN.md`](./PLAN.md#phase-및-github-이슈)를 참고하세요.
- 기여 방법은 [`CONTRIBUTING.md`](./CONTRIBUTING.md)를 참고하세요.

## 라이선스

[AGPL-3.0](./LICENSE)입니다. 이 서버(또는 수정본)를 네트워크 서비스로 돌려
다른 사람에게 제공하는 경우, AGPL에 따라 해당 소스코드도 함께 공개해야 합니다.