Skip to main content
Glama
kyowon1108

activities-mcp

by kyowon1108
README.md
# activities-mcp

공개 활동·공모전·경진대회 목록의 **공개 메타데이터만** 로컬 SQLite에
캐시하고, 읽기 전용 MCP 도구 5개로 STDIO에 제공하는 서버입니다. 수집은
명시적으로 실행하는 CLI 작업이며, MCP 서버는 이미 저장된 캐시만 읽습니다.

## 제공 MCP 도구

| 도구 | 입력 | 반환 |
| --- | --- | --- |
| `list_activity_sources` | — | 7개 소스의 활성 상태, 캐시 건수, 마지막 안전 상태 |
| `list_latest_activities` | `source?`, `normalized_status?`, 날짜 범위, `limit`, `offset` | 최신 캐시 메타데이터 |
| `search_activities` | `query`, 선택 필터, `limit`, `offset` | SQLite FTS5 검색 결과 |
| `list_activity_changes` | `discovered_after?` 또는 `cursor?`, `limit` | 발견·갱신 변경 피드 |
| `get_activity` | `activity_id` | 단건 캐시 메타데이터 |

모든 도구는 읽기 전용입니다. 입력 오류는 `invalid_input`, 로컬 캐시 접근
실패는 `storage_error`, 존재하지 않는 항목은 `not_found`의 안정적인 응답
계약으로 반환하며, SQL·경로·로컬 예외 세부 정보는 노출하지 않습니다.

## 지원 소스와 수집 범위

현재 지원하는 소스는 아래 7개입니다. 모든 요청은 HTTPS, 허용 호스트·경로·
쿼리 정책, 수동 리다이렉트 검증, 응답 크기 제한, 지연, 제한된 재시도 아래에서
수행합니다. 401·403·429, CAPTCHA, 정책 위반, 파서 드리프트는 우회하지 않고
해당 소스를 저하 상태로 기록합니다.

| 소스 | 수집 방식 | 기본 상태 |
| --- | --- | --- |
| Linkareer | 공개 활동·공모전 HTML 목록의 최신 1페이지씩 | 활성. STEM, 로그인, 프로필, 구매, GraphQL은 요청하지 않음 |
| Wevity | 공개 카테고리 HTML 목록 | 활성. 공개 카드 메타데이터만 수집 |
| ContestKorea | 공개 목록 HTML | 활성. WAF·차단 응답은 저하로 종료 |
| Thinkgood | 공개 `POST /thinkgood/user/contest/subList.do` JSON 목록 | 활성. 허용된 목록 필드만 읽고 파일·업로드·전자책은 제외 |
| DACON | 공개 대회 목록 HTML | 활성. 데이터셋, 제출, 코드, 리더보드, 프로필은 제외 |
| AI Factory | 공개 SSR/Next RSC 대회·과제 목록 | 활성. 목록의 허용 메타데이터만 읽고 상세 요청 없음 |
| Ticketa | 공개 `sitemap.xml` 후 최신 후보 1건의 공개 JSON-LD 이벤트 상세 | 활성. 티켓·주문·결제·사용자·Supabase 영역은 제외 |

세부 정책 근거와 점검 시각은 [docs/source-policy.md](docs/source-policy.md)를
참조하세요. 이 정책은 기술적 통제이며, 각 사이트의 약관·robots·법적 조건에
대한 허가를 주장하지 않습니다.

## 기술 구성

- 런타임: Python 3.13+, `uv`
- 서버·저장: MCP SDK (STDIO), SQLite, FTS5
- 수집: `httpx2`, BeautifulSoup4, defusedxml
- 설정·CLI: Pydantic, pydantic-settings, Typer
- 품질: pytest, Ruff, basedpyright

## 설치와 로컬 실행

```sh
uv sync
uv run activities-mcp --help
```

처음에는 데이터베이스를 만들고 7개 소스 상태를 시드합니다. 이 명령은 네트워크
수집을 실행하지 않습니다.

```sh
uv run activities-mcp init-db --db-path data/activities.db
uv run activities-mcp status  --db-path data/activities.db
```

`refresh`는 각 활성 소스의 보수적인 최신 목록 계획만 수행합니다.

```sh
uv run activities-mcp refresh --db-path data/activities.db
```

`backfill`은 운영자가 명시적으로 요청할 때만 사용합니다. 최대 3페이지의
지원되는 공개 GET 목록만 계획하며, POST 페이지네이션을 추측해서 만들지 않습니다.

```sh
uv run activities-mcp backfill --pages 1 --db-path data/activities.db
```

MCP 서버는 기존 데이터베이스가 있어야 하며 STDIO만 사용합니다.

```sh
uv run activities-mcp serve --db-path data/activities.db
```

`status`와 `serve`는 데이터베이스를 새로 만들지 않습니다. 실수로 MCP 조회가
수집 작업을 시작하지 않도록 하기 위한 경계입니다.

## 설정

환경변수는 검증된 기본값을 제공하고, 명시한 `--db-path`가 우선합니다.

| 환경변수 | 의미 | 기본값 |
| --- | --- | --- |
| `ACTIVITIES_MCP_DB_PATH` | SQLite 캐시 경로 | `activities.db` |
| `ACTIVITIES_MCP_DELAY_SECONDS` | 요청 전 지연 시간 | `3.0`초 |
| `ACTIVITIES_MCP_MAXIMUM_RESPONSE_BYTES` | 응답 본문 상한 | 4 MiB |

예를 들어 별도 경로와 짧은 로컬 테스트 지연을 사용하려면 다음과 같이 실행합니다.

```sh
ACTIVITIES_MCP_DB_PATH=data/activities.db \
ACTIVITIES_MCP_DELAY_SECONDS=3 \
uv run activities-mcp refresh
```

## MCP 클라이언트 등록

GUI 클라이언트는 셸의 PATH를 상속하지 않을 수 있으므로 `command`에는 `uv`의
절대 경로를 권장합니다. 아래의 `/Users/me/.local/bin/uv`, 프로젝트 경로, DB
경로를 실제 환경에 맞게 바꾸세요.

### Claude Desktop

Claude Desktop의 MCP 설정 파일에 다음 서버를 추가합니다.

```json
{
  "mcpServers": {
    "activities": {
      "command": "/Users/me/.local/bin/uv",
      "args": [
        "run", "--project", "/path/to/activities-mcp",
        "activities-mcp", "serve", "--db-path",
        "/path/to/activities-mcp/data/activities.db"
      ]
    }
  }
}
```

### ChatGPT/Codex

ChatGPT/Codex의 로컬 MCP 서버 설정에 같은 STDIO 명령을 등록합니다.

```json
{
  "mcpServers": {
    "activities": {
      "command": "/Users/me/.local/bin/uv",
      "args": [
        "run", "--project", "/path/to/activities-mcp",
        "activities-mcp", "serve", "--db-path",
        "/path/to/activities-mcp/data/activities.db"
      ]
    }
  }
}
```

### Hermes

Hermes의 로컬 MCP 서버 설정에도 다음 STDIO 구성을 사용합니다.

```json
{
  "mcpServers": {
    "activities": {
      "command": "/Users/me/.local/bin/uv",
      "args": [
        "run", "--project", "/path/to/activities-mcp",
        "activities-mcp", "serve", "--db-path",
        "/path/to/activities-mcp/data/activities.db"
      ]
    }
  }
}
```

세 클라이언트 모두 먼저 `init-db`를 한 번 실행하고, 캐시 파일 경로가 서버
프로세스에서 읽을 수 있는 위치인지 확인해야 합니다.

## 스케줄러와 GitHub Actions

이 프로그램에는 내부 스케줄러가 없습니다. 운영 환경의 cron, systemd timer,
워크플로, 컨테이너 스케줄러처럼 관리자가 선택한 저빈도 실행기로 `refresh`를
호출하세요.

```sh
0 3 * * 3 cd /path/to/activities-mcp && /Users/me/.local/bin/uv run activities-mcp refresh --db-path data/activities.db
```

저장소의 `.github/workflows/refresh.yml`은 수동 실행과 주간 cron을 제공합니다.
워크플로는 이전 `activities.db` 캐시를 복원한 뒤 `init-db`(멱등), `refresh`를
실행하고 새 캐시를 보관합니다. 모든 액션은 커밋 SHA로 고정되어 있으며 권한은
읽기 전용입니다. 운영 정책과 백필 지침은 [docs/scheduler.md](docs/scheduler.md)를
참조하세요.

## 캐시 보존과 실패 처리

수집 성공은 해당 소스의 유효한 공개 레코드를 upsert하고 안전 상태를 갱신합니다.
반대로 타임아웃, 차단, CAPTCHA, 파서 드리프트, 빈 파싱 결과, 개별 상세 실패는
기존 캐시를 삭제하지 않습니다. 소스 상태만 `degraded`와 안전 오류 코드로
바뀌며, 다른 소스의 수집은 계속 진행합니다. 따라서 일시적 외부 장애 중에도 MCP
클라이언트는 마지막으로 검증된 캐시를 계속 읽을 수 있습니다.

## 개인정보·비수집 원칙

이 프로젝트는 목록에서 공개된 제목, 주최자, 날짜, 상태, 카테고리, 태그, 위치,
공개 카운터와 정규 URL 등 최소 메타데이터만 저장합니다. 다음은 요청·저장·반환하지
않습니다.

- 로그인 세션, 계정, 프로필, 개인 연락처, 이메일, 전화번호
- 결제, 구매, 주문, 티켓, 제출물, 데이터셋, 첨부파일, 이미지, 본문
- API 키, Supabase 비밀값, 쿠키, 인증 헤더
- LLM 프롬프트, 대화 내용, 추천·랭킹을 위한 사용자 행동 데이터

테스트 파서 fixture도 키·이메일·전화번호·실제 비공개 응답을 포함하지 않도록
검사합니다.

## MCP와 LLM의 책임 경계

MCP 서버의 책임은 **로컬 캐시의 사실적 메타데이터 조회**뿐입니다. 서버는 LLM을
호출하지 않고, 추천·판단·지원서 작성·일정 등록·자동 제출·외부 변경을 수행하지
않습니다. Claude, ChatGPT/Codex, Hermes 같은 클라이언트의 LLM은 반환된 사실을
어떻게 대화에 활용할지 결정할 수 있지만, 그 판단과 생성 결과는 이 서버의 수집·
저장·정책 경계 밖에 있습니다.