Skip to main content
Glama
README.md
# SISTER MCP — otomasi pengisian BKD ke SISTER

Perangkat lunak untuk mengisi BKD ke SISTER secara otomatis. Pemakai hanya perlu
menyediakan **username, password, dan data** — login SSO, pengambilan token CSRF,
penerjemahan label menjadi kode SISTER, dan unggah berkas ditangani di sini.

Empat bentuk pemakaian di atas inti yang sama — proyek lain bisa membangun
aplikasinya sendiri di atas mana pun dari keempatnya:

| Bentuk   | Dipakai lewat                     | Referensi                  | Untuk siapa                      |
|----------|-----------------------------------|----------------------------|----------------------------------|
| Pustaka  | `from sister_mcp import …`        | [bagian di bawah](#sebagai-pustaka) | dipakai kode Python lain  |
| REST API | `sister_mcp.api:app` (ASGI)       | [docs/API.md](docs/API.md) | dipanggil aplikasi lain          |
| MCP      | `sister-mcp`                      | [docs/MCP.md](docs/MCP.md) | dipakai langsung oleh asisten AI |
| CLI      | `sister isi`                      | [docs/CLI.md](docs/CLI.md) | dipakai sendiri dari terminal    |

Menu yang sudah didukung: **pengabdian**, **publikasi**, **kepanitiaan**,
**anggota_profesi**. Contoh datanya ada di [examples/](examples/).

Selain mengisi data tridharma, aplikasi ini juga mendaftarkan **bukti penugasan**
pada Layanan BKD → Rekap Kegiatan → Rincian, untuk kegiatan yang ditarik SISTER
sendiri dari Feeder (mata kuliah yang diampu).

## Mulai cepat

Empat langkah, dari nol sampai satu kegiatan masuk ke SISTER.

### 1. Ambil dan siapkan

Butuh Python 3.10 atau lebih baru.

```bash
git clone https://github.com/alfa-yohannis/sister-mcp.git
cd sister-mcp
./sister uji           # menyiapkan virtualenv, lalu menjalankan unit test
```

Jalankan pertama memakan waktu sebentar karena `./sister` membuat `.venv` dan
memasang dependensinya sendiri. Kalau selesai dengan `69 passed`, semuanya beres —
unit test tidak menyentuh jaringan, jadi tahap ini belum butuh akun SISTER.

### 2. Isi kredensial

```bash
cp .env.contoh .env
$EDITOR .env           # isi SISTER_USERNAME dan SISTER_PASSWORD
```

`.env` sudah masuk `.gitignore`, jadi tidak akan ikut ter-commit.

### 3. Coba tanpa mengirim apa pun

`--uji-coba` login ke SISTER lalu memperlihatkan field apa yang akan dikirim,
tanpa menyimpan satu pun record:

```bash
./sister isi publikasi examples/publikasi.json --uji-coba
```

Baris pertamanya sekaligus jadi bukti kredensialnya benar:

```
Login SISTER OK — menu publikasi

[1] A Comparative Study of Relational and Hybrid Object-Relational Storage…
  action : https://sister-pt.kemdiktisaintek.go.id/tridharma/publikasi
  judul                            = A Comparative Study of …
  …
```

Kalau kredensialnya salah, yang muncul `Login ditolak — periksa username/password
SISTER`. Kalau `.env` belum diisi, `Variabel belum ada di …/.env`.

### 4. Kirim sungguhan

```bash
./sister isi publikasi examples/publikasi.json
```

Ganti `examples/publikasi.json` dengan berkas Anda sendiri — bentuknya mengikuti
[contoh di examples/](examples/), dan path dokumen di dalamnya boleh relatif
terhadap letak berkas JSON itu.

Mengulang berkas yang sama aman: kegiatan yang judulnya sudah ada dilewati, bukan
digandakan.

## Memasang sebagai paket

Untuk memakainya dari proyek lain — sebagai pustaka, REST API, atau MCP — pasang
paketnya. Belum ada di PyPI, jadi pasang langsung dari repositorinya:

```bash
pip install "git+https://github.com/alfa-yohannis/sister-mcp.git"                  # pustaka saja
pip install "sister-mcp[api] @ git+https://github.com/alfa-yohannis/sister-mcp.git"    # + REST API
pip install "sister-mcp[mcp] @ git+https://github.com/alfa-yohannis/sister-mcp.git"    # + server MCP
pip install "sister-mcp[semua] @ git+https://github.com/alfa-yohannis/sister-mcp.git"  # ketiganya
```

Ikut terpasang dua perintah: `sister` (CLI) dan `sister-mcp` (server MCP).

`.env`, `sister.yaml`, dan `data/` dibaca dari **direktori kerja proyek yang
memakai**, bukan dari dalam paketnya — jadi tiap proyek punya kredensial dan
setelannya sendiri.

## Menjalankan tiap bentuk

Ketiganya berdiri di atas inti yang sama dan bisa dipakai dari proyek lain.
Masing-masing lengkap: pasang → setel → jalankan → pastikan.

### Sebagai pustaka

**Pasang**

```bash
pip install "git+https://github.com/alfa-yohannis/sister-mcp.git"
```

**Setel** — di direktori proyek Anda sendiri:

```bash
printf 'SISTER_USERNAME=nama@contoh.ac.id\nSISTER_PASSWORD=rahasia\n' > .env
```

**Jalankan**

```python
from sister_mcp import SisterClient, buat_menu, model_menu

client = SisterClient().login()             # kredensial dari .env
menu = buat_menu("publikasi", client)

data = {"judul": "…", "kategori_kegiatan": "120902", "…": "…"}
hasil = menu.simpan(menu.model(**data), uji_coba=True)   # tidak menyimpan apa pun
print(hasil["isian"])
```

Kredensial juga boleh diberikan langsung — `SisterClient("nama@contoh.ac.id",
"rahasia")` — begitu pula alamat instance lewat `base_url=`.

Bentuk `data` mengikuti model menunya. Model dan kelas menunya bisa dipakai
langsung, dan skemanya bisa dibaca tanpa jaringan:

```python
from sister_mcp import Publikasi, MenuPublikasi, MENU_TERSEDIA, SisterError

model_menu("publikasi").model_json_schema()   # field apa saja, mana yang wajib
```

**Pastikan** — contoh utuh yang bisa langsung dijalankan dan tidak menyentuh
jaringan sama sekali:

```bash
python examples/pakai_pustaka.py
```

### Sebagai REST API

**Pasang**

```bash
pip install "sister-mcp[api] @ git+https://github.com/alfa-yohannis/sister-mcp.git"
```

**Jalankan**

```bash
sister api                 # http://localhost:8000
PORT=9000 sister api       # port lain
./sister api               # dari klona repo, tanpa memasang
```

Tidak perlu `.env`: kredensial dikirim per permintaan, jadi satu server bisa
melayani banyak dosen.

**Pastikan**

```bash
curl localhost:8000/menu
# {"menu":["anggota_profesi","kepanitiaan","pengabdian","publikasi"]}

curl -X POST localhost:8000/sesi \
     -H "Content-Type: application/json" \
     -d '{"username":"...","password":"..."}'
# {"ok":true,"landing_url":"https://sister-pt.kemdiktisaintek.go.id"}
```

Swagger UI ada di <http://localhost:8000/docs>. Mengisi satu kegiatan:

```bash
curl -X POST "localhost:8000/publikasi?uji_coba=true" \
     -H "Content-Type: application/json" \
     -d '{"username":"...","password":"...","data":{...}}'
```

Aplikasinya ASGI biasa, jadi bisa di-mount ke aplikasi FastAPI lain:

```python
from fastapi import FastAPI
from sister_mcp.api import app as sister_api

app = FastAPI()
app.mount("/sister", sister_api)
```

Selengkapnya di [docs/API.md](docs/API.md).

### Sebagai server MCP

**Pasang dan daftarkan**

```bash
pip install "sister-mcp[mcp] @ git+https://github.com/alfa-yohannis/sister-mcp.git"
claude mcp add sister -- sister-mcp
```

Dari klona repo tanpa memasang — klien MCP memanggil lewat path absolut:

```bash
claude mcp add sister -- /path/ke/sister-mcp/sister mcp
```

Untuk klien MCP lain, konfigurasinya berupa JSON; `env` di situ jalur setelannya
(lihat [docs/MCP.md](docs/MCP.md)).

**Setel** — kredensial boleh ditaruh di `.env` supaya asisten tidak perlu
memegangnya sama sekali, atau dikirim per pemanggilan tool.

**Pastikan**

```bash
claude mcp list            # 'sister' harus muncul sebagai connected
```

Lalu minta ke asisten dengan bahasa biasa — kumpulan prompt siap salin ada di
[examples/prompt_mcp.md](examples/prompt_mcp.md):

> Di folder 2026-08/pengabdian ada proposal, laporan akhir, surat tugas, undangan,
> dan surat keterangan mitra. Baca semuanya, susun data pengabdiannya, lalu isikan
> ke SISTER. Uji coba dulu.

Delapan tool tersedia: `cek_login`, `daftar_menu`, `skema_menu`, `daftar_kegiatan`,
`referensi`, `cari_dosen`, `dokumen_kegiatan`, dan `isi_kegiatan`. Yang terakhir
bawaannya `uji_coba=True`, jadi pemanggilan pertama tidak pernah menulis ke SISTER.
Selengkapnya di [docs/MCP.md](docs/MCP.md).

## Kredensial dan setelan

Aplikasi ini berdiri sendiri: kedua berkas di bawah dicari **hanya di
direktorinya sendiri**, tidak pernah di direktori induk. Klona repositori ini ke
mana pun, dan tidak ada proyek lain yang perlu ada di sekitarnya.

Yang rahasia dipisah dari yang bukan, supaya setelan bisa dibagikan sedangkan
kata sandi tidak pernah ikut ter-commit.

**`.env`** — kredensial saja (salin dari [.env.contoh](.env.contoh)):

```
SISTER_USERNAME=...
SISTER_PASSWORD=...
```

**`sister.yaml`** — setelan non-rahasia (salin dari
[sister.contoh.yaml](sister.contoh.yaml)). Semuanya opsional; tanpa berkas ini
pun aplikasi berjalan dengan nilai bawaan:

```yaml
base_url: https://sister-pt.kemdiktisaintek.go.id
website: https://sister.kemdiktisaintek.go.id/auth-gateway/login
afiliasi: Universitas Contoh      # afiliasi penulis pada menu publikasi
waktu_tunggu_unggah: 300          # detik; naikkan kalau lampirannya besar
```

Alamat SISTER ditaruh di sini karena memang jarang berubah — tapi bukan tidak
pernah. Kalau Kemdiktisaintek memindahkannya, cukup berkas ini yang disunting.

Urutan yang menang, dari paling khusus:

```
argumen pemanggil  ->  environment  ->  .env  ->  sister.yaml  ->  nilai bawaan
```

Nama kunci di `sister.yaml` sama dengan nama environment-nya tanpa awalan
`SISTER_` dan huruf kecil: `SISTER_BASE_URL` ⇄ `base_url`.

Kalau beberapa aplikasi memang ingin berbagi satu berkas konfigurasi, tunjuk
direktorinya lewat `SISTER_KONFIG_DIR`. Itu pilihan, bukan syarat — dan `.env`
serta `sister.yaml` milik aplikasi ini sendiri tetap menang atasnya.

REST API dan MCP juga bisa menerima kredensial — dan alamat instance — per
permintaan, sehingga kedua berkas itu tidak wajib untuk keduanya.

## Perintah

```bash
./sister isi <menu> <berkas.json> [--uji-coba]   # isi data; --uji-coba tidak menyimpan apa pun
./sister rapikan <menu> "<judul>" --lihat        # periksa dokumen ganda pada satu record
./sister bukti --rincian "<url rincian BKD>" --tautan "<url bukti>" --lihat
./sister petakan /tridharma/<menu>/create        # petakan form saat menambah menu baru
./sister api                                      # REST API di http://localhost:8000
./sister mcp                                      # MCP server lewat stdio
./sister uji                                      # unit test (tanpa jaringan)
```

Selalu jalankan `--uji-coba` lebih dulu. Mengulang berkas yang sama aman:
kegiatan yang judulnya sudah ada dilewati, bukan digandakan.

## Rancangan

Semua menu tridharma SISTER memakai alur yang persis sama:

```
buka form create → susun isian → POST → pastikan tersimpan → lengkapi dokumen
```

Alur itu ditulis satu kali di `MenuTridharma.simpan()`. Tiap menu adalah
subkelasnya yang hanya mengisi bagian yang memang berbeda:

```
MenuTridharma (abstrak)                    sister_mcp/tridharma.py
├── MenuPengabdian     + model Pengabdian     sister_mcp/pengabdian.py
├── MenuPublikasi      + model Publikasi      sister_mcp/publikasi.py
├── MenuKepanitiaan    + model Kepanitiaan    sister_mcp/kepanitiaan.py
└── MenuAnggotaProfesi + model AnggotaProfesi sister_mcp/anggota_profesi.py
```

Yang wajib disediakan subkelas hanya dua: `judul_kegiatan()` dan `susun_isian()`.
Perilaku yang berbeda sendiri ditimpa lewat hook — `siapkan_form()` dipakai
publikasi karena isian detailnya baru dimuat setelah kategori dipilih, dan
`sudah_tersimpan()` dipakai menu yang kolom daftarnya bukan "Judul".

Menu baru cukup didaftarkan di `src/sister_mcp/menu.py`; CLI, REST API, dan MCP langsung
ikut mengenalinya tanpa perubahan lain. Langkah menambahnya:

```bash
./sister petakan /tridharma/pengelola_jurnal/create --all-options   # 1. petakan formnya
# 2. tulis subkelas MenuTridharma + model datanya
# 3. daftarkan di sister_mcp/menu.py
# 4. tambahkan uji penyusunan isiannya di tests/test_menu.py
```

## Bentuk data

Skema lengkap tiap menu bisa dilihat lewat `GET /menu/{nama}/skema` (REST API)
atau tool `skema_menu` (MCP). Contoh siap pakai ada di [examples/](examples/):

```jsonc
// examples/kepanitiaan.json
{
  "nama_panitia": "Pengulas di Jurnal Internasional Cluster Computing",  // maksimal 80 karakter
  "kategori_kegiatan": "140702",        // kode atau labelnya
  "jenis_panitia": "Panitia lainnya",
  "tingkat": "Internasional",
  "instansi": "Springer Nature",
  "nomor_sk_tugas": "0",
  "tanggal_mulai": "2026-07-15",        // boleh dd/mm/yyyy
  "tanggal_selesai": "2026-07-15",
  "anggota": [{ "nama": "SPIDER MAN", "peran": "Pengulas" }],
  "dokumen": [{ "file": "berkas/sertifikat.pdf", "nama": "Sertifikat", "jenis": "Lainnya" }]
}
```

Nama dosen dicocokkan otomatis ke daftar dosen perguruan tinggi (tidak perlu tahu
id-nya), begitu pula label jenis dokumen, kategori kegiatan, dan pilihan select
lainnya. Dosen pemilik akun wajib ikut tercantum karena SISTER menaruhnya sebagai
baris permanen paling atas. Tahun pada menu pengabdian mengikuti tahun ajaran:
`2025` berarti 2025/2026.

## Isi berkas

| Berkas                       | Kegunaan                                                           |
|------------------------------|--------------------------------------------------------------------|
| `pyproject.toml`             | metadata paket, dependensi, dan kedua perintahnya                  |
| `sister`                     | pembungkus untuk memakai dari klona repo tanpa memasang            |
| `.env.contoh`                | contoh berkas kredensial; salin jadi `.env`                        |
| `sister.contoh.yaml`         | contoh berkas setelan; salin jadi `sister.yaml`                    |
| `src/sister_mcp/__init__.py` | permukaan pustaka: apa saja yang boleh diimpor proyek lain          |
| `src/sister_mcp/cli.py`      | perintah `sister`: menyatukan seluruh subperintah                  |
| `src/sister_mcp/dependensi.py`      | pemeriksa & pemasang paket saat pertama dijalankan                 |
| `src/sister_mcp/konfigurasi.py`     | baca `.env` + `sister.yaml`, sesi HTTP, simpan hasil ke `data/`    |
| `src/sister_mcp/sister_client.py`   | inti HTTP: login SSO, baca/kirim form, cari dosen, daftar & dokumen |
| `src/sister_mcp/tridharma.py`       | kelas dasar `MenuTridharma` + model `Dokumen` + pemformat nilai  |
| `src/sister_mcp/pengabdian.py`      | `MenuPengabdian` + model `Pengabdian`                            |
| `src/sister_mcp/publikasi.py`       | `MenuPublikasi` + model `Publikasi` (form dinamis per kategori)  |
| `src/sister_mcp/kepanitiaan.py`     | `MenuKepanitiaan` + model `Kepanitiaan`                          |
| `src/sister_mcp/anggota_profesi.py` | `MenuAnggotaProfesi` + model `AnggotaProfesi`                    |
| `src/sister_mcp/menu.py`            | daftar menu yang didukung + pabrik pembuatnya                    |
| `src/sister_mcp/layanan_bkd.py`     | `RekapBKD`: kegiatan pada rincian BKD + pendaftaran buktinya     |
| `src/sister_mcp/isi_bkd.py`         | CLI pengisian data tridharma dari berkas JSON                    |
| `src/sister_mcp/isi_bukti_bkd.py`   | CLI pendaftaran bukti penugasan pada rekap BKD                   |
| `src/sister_mcp/rapikan_dokumen.py` | CLI penghapus dokumen ganda pada sebuah record                   |
| `src/sister_mcp/api.py`             | REST API (FastAPI)                                               |
| `src/sister_mcp/mcp_server.py`      | MCP server                                                       |
| `src/sister_mcp/petakan_form.py`    | alat bantu: petakan form SISTER saat menambah menu baru           |
| `tests/`                     | unit test, semuanya berjalan tanpa jaringan                      |
| `docs/`                      | referensi CLI, REST API, dan MCP                                 |
| `examples/`                  | contoh data tiap menu + contoh klien Python dan `curl`           |
| `data/`                      | hasil tarikan dan berkas sementara                               |

## Catatan alur login SISTER

Autentikasi SISTER memakai OAuth2/OIDC (Ory Hydra), jadi tidak bisa POST langsung
ke satu endpoint. Rantainya:

1. `sister.kemdiktisaintek.go.id/auth-gateway/login`
2. → `sister-auth-gateway.../oauth2/auth?client_id=...` (menerbitkan `login_challenge`)
3. → `auth-ssosister.../v2/login` (form berisi `_csrf` + `challenge`)
4. → POST kredensial → consent → `/auth-gateway/callback`
5. → mendarat di `sister-pt.kemdiktisaintek.go.id` (sesi aktif)

`requests.Session` mengikuti seluruh redirect itu otomatis. Kuncinya: ambil ulang
**semua** `<input hidden>` dari form SSO (terutama `_csrf` dan `challenge`) lalu
timpa username/password — bukan mengirim dua field itu saja.

## Catatan alur pengisian data

SISTER tidak punya REST API; halamannya server-rendered (Laravel + jQuery). Maka
setiap pengisian selalu: GET form create → POST multipart ke action-nya → baca
ulang halaman daftar untuk memastikan datanya benar-benar tersimpan.

Perilaku SISTER yang sudah ditemukan dan ditangani:

- **Pesan validasi tidak dikirim sebagai JSON.** Kalau ada isian wajib yang kosong,
  server membalas halaman formnya lagi (HTTP 200) dengan pesan tertanam sebagai kode
  JS `error_string_array[0] = '...'`. `baca_pesan_validasi()` yang menerjemahkannya.
- **Unggahan besar bisa terpotong diam-diam.** Mengirim lima PDF (±5 MB) sekaligus
  membuat berkas terakhir hilang tanpa pesan error. Karena itu setelah record
  tersimpan, dokumen yang diminta dibandingkan dengan yang benar-benar menempel,
  lalu sisanya diunggah satu per satu dan diperiksa ulang.
- **Unggahan susulan sesekali gagal tanpa pesan.** Server membalas 200 tapi
  dokumennya tidak tersimpan, jadi hasilnya selalu diverifikasi dan diulang sekali.
- **`nm_panitia` dibatasi ±80 karakter.** Lebih dari itu SISTER membalas HTTP 500
  tanpa pesan apa pun, jadi dicegat lebih dulu di `MenuKepanitiaan`.
- **URL hapus hanya menerima DELETE.** Membukanya dengan GET dibalas HTTP 500;
  yang benar POST dengan `_method=DELETE` memakai token sesi.
- **Sebagian form dimuat belakangan.** Isian detail publikasi baru dikirim server
  lewat `POST /tridharma/publikasi/get_form` setelah kategori dipilih, jadi form
  harus dilengkapi dulu (`merge_form_fragment`) sebelum opsinya bisa dipakai.
- **Endpoint pencarian dosen berbeda antar menu.** Ada yang di
  `/tridharma/<menu>/get_data_ptk`, ada yang di `/common_global_service/get_data_ptk`;
  `lookup_dosen()` mencoba keduanya.
- **Bukti BKD hanya berupa tautan.** Form Bukti Penugasan di rekap BKD tidak
  menerima unggahan berkas; yang wajib diisi adalah Tautan Dokumen. Berkasnya
  harus sudah tersimpan di tempat yang bisa dibuka asesor (mis. folder Google
  Drive yang di-share), lalu tautannya yang didaftarkan. Nama dokumen dibatasi
  60 karakter dan keterangan 200 karakter.
- **Dokumen bukti tidak bisa disunting.** Setelah terdaftar hanya tersedia aksi
  Hapus, jadi perbaikan berarti hapus lalu daftarkan ulang.
- **Tiga menu tidak bisa diisi sama sekali.** `pengajaran`, `bimbingan_mahasiswa`,
  dan `pengujian_mahasiswa` ditarik otomatis dari Feeder PDDikti — halaman
  `create`-nya bahkan tidak memuat form.

## Konvensi penulisan skrip

- Satu kelas untuk satu tanggung jawab; alur bersama tinggal di kelas dasar,
  yang khas per menu ada di subkelasnya.
- Setiap kelas, method, dan fungsi punya docstring.
- Fungsi ditulis datar — tanpa fungsi bersarang di dalam fungsi.
- Nama variabel dan fungsi ditulis utuh dan deskriptif (`http_session`, `login_form`),
  bukan singkatan satu-dua huruf (`s`, `cfg`, `inp`).
- Komentar hanya untuk hal yang tidak terbaca dari kodenya, bukan mengulang isi baris.
- Nilai rahasia tidak pernah dicetak; yang ditampilkan hanya nama cookie.