OMOS-MCP
# OMOS-MCP
MCP server ให้ AI agent (Claude, ChatGPT, Cursor ฯลฯ) อ่านเอกสารโปรเจคใน **OMOS Shared Drive** เพื่อตอบคำถาม — ทุกคำตอบอ้างอิงไฟล์ต้นทางพร้อม link Google Drive
โครงสร้าง Drive: **โฟลเดอร์ชั้นบนสุด = 1 โปรเจค (ชื่อโฟลเดอร์คือชื่อโปรเจค)** ข้างในจะจัดยังไงก็ได้ ไม่บังคับ structure
```
OMOS/
├── Project A/ ← ชื่อโปรเจค
│ └── ... (อะไรก็ได้)
└── Project B/
└── ...
```
## Tools
| Tool | คำอธิบาย |
|------|----------|
| `omos_index` | หาโปรเจคจากชื่อ — ใส่ `filter` = คำที่ผู้ใช้พูด (ไทย/อังกฤษ) ไม่ใส่จะได้แค่ตัวอย่าง 10 ชื่อ (ปรับ `limit` ได้) agent เรียกอันนี้ก่อนเสมอ |
| `omos_list` | ไฟล์ทั้งหมดในโปรเจคที่ระบุ พร้อม id + link (ใส่ `subfolder` เพื่อดูเฉพาะบางส่วน) |
| `omos_search` | ค้นหา full-text ทั้ง Drive หรือเจาะเฉพาะโปรเจค |
| `omos_read` | อ่านไฟล์เป็น text — Google Docs/Sheets/Slides, PDF, .docx, .xlsx, md/text — ส่วนรูปภาพส่งเป็นรูปให้ agent ดูตรงๆ |
| `omos_refresh` | โหลดรายชื่อโปรเจคใหม่ทันที (ปกติ cache 5 นาที) |
Drive จริงมีหลักร้อยโปรเจคและไฟล์หลักหมื่น server จึงไม่เดินทั้ง Drive: อ่านเฉพาะที่ถูกถามถึง — `omos_index` = 1 API call, `omos_list` เดินเฉพาะ subtree ของโปรเจคนั้น, path ของผลลัพธ์ search/read resolve ทีละไฟล์ตอนใช้จริง
Flow ที่ฝังไว้ใน server instructions: `omos_index(filter=คำที่ผู้ใช้พูด)` → เทียบชื่อโปรเจค (ไม่ชัดให้ถามยืนยัน ห้ามเดาเงียบๆ) → `omos_list` หรือ `omos_search` เจาะโปรเจคนั้น → `omos_read` → ทุกคำตอบต้องอ้างอิงชื่อไฟล์พร้อม link
Drive จริงมีเกือบ 500 โปรเจค การเทชื่อทั้งหมดใส่ context ไม่ช่วยให้หาเจอ `omos_index` จึงเน้นให้ค้นด้วย `filter` แทน
## ตั้งค่าครั้งเดียว: Service Account
server เข้าถึง Drive ด้วย service account (ไม่ต้องให้ทุกคน login):
1. เข้า [console.cloud.google.com](https://console.cloud.google.com) → สร้าง project ใหม่ (หรือใช้ที่มีอยู่)
2. **APIs & Services → Library** → ค้นหา **Google Drive API** → กด **Enable**
3. **IAM & Admin → Service Accounts → Create Service Account** → ตั้งชื่อ เช่น `omos-mcp` → กด Create (ข้ามขั้นตอน role ได้เลย ไม่ต้องให้สิทธิ์อะไร)
4. เข้า service account ที่สร้าง → แท็บ **Keys → Add Key → Create new key → JSON** → ไฟล์ key จะดาวน์โหลดมา
5. copy อีเมลของ service account (หน้าตา `omos-mcp@<project>.iam.gserviceaccount.com`) ไป**แชร์ Shared Drive / โฟลเดอร์ OMOS** ให้อีเมลนี้เป็น **Viewer** (ถ้าจะใช้ [รับ transcript อัตโนมัติ](#รับ-transcript-อัตโนมัติ-post-transcripts) ต้องให้เป็น **Content manager** เพราะต้องเขียนไฟล์ได้)
6. หา **folder id** ของ root OMOS: เปิดโฟลเดอร์ใน browser แล้วดู URL `https://drive.google.com/drive/folders/<อันนี้คือ id>`
## Environment Variables
| ตัวแปร | คำอธิบาย |
|--------|----------|
| `OMOS_ROOT_FOLDER_ID` | folder id ของ root OMOS (จากขั้นตอนที่ 6) — **บังคับ** |
| `GOOGLE_SERVICE_ACCOUNT_JSON` | เนื้อไฟล์ key JSON ทั้งก้อน หรือ path ไปยังไฟล์ — **บังคับ** |
| `OMOS_AUTH_TOKEN` | (HTTP mode) bearer token ที่ client ต้องส่งมา |
| `OMOS_INDEX_TTL` | อายุ cache ของรายชื่อโปรเจค เป็นวินาที (default 300) |
| `OMOS_INGEST_TOKEN` | token สำหรับ `POST /transcripts` (แยกจาก MCP token) — ไม่ตั้ง = endpoint ปิดใช้งาน |
| `OMOS_TRANSCRIPT_FOLDER` | ชื่อ subfolder ที่เก็บ transcript (default `Meeting Transcripts`) |
| `OMOS_DEADLINE` | เวลาสูงสุดต่อ 1 tool call เป็นวินาที (default 20) — เกินแล้วคืนผลเท่าที่ได้พร้อมคำเตือน |
| `OMOS_HTTP_TIMEOUT` | timeout ต่อ 1 request ที่ยิงไป Drive เป็นวินาที (default 20) |
| `OAUTH_ISSUER` / `OAUTH_AUDIENCE` / `PUBLIC_URL` | (HTTP mode) เปิดโหมด OAuth สำหรับ Claude.ai / ChatGPT เว็บ |
| `PORT` | (HTTP mode) port ที่ฟัง (default 8000) |
## รัน local
ติดตั้ง [uv](https://docs.astral.sh/uv/) ก่อน (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
**1) ตั้งค่า `.env`** (server โหลดให้อัตโนมัติจาก root ของ repo):
```bash
cp .env.example .env
```
แก้ `.env` ใส่ `OMOS_ROOT_FOLDER_ID` และ path ไฟล์ key (เช่นวางไฟล์ key ไว้ที่ `./service-account.json` — gitignore ไว้ให้แล้ว)
**2) เชื่อมกับ Claude Code (stdio):**
```bash
claude mcp add omos -- uv run --directory "/path/to/OMOS-MCP" omos-mcp
```
Claude Desktop / Cursor / Windsurf — ใส่ใน MCP config:
```json
{
"mcpServers": {
"omos": {
"command": "uv",
"args": ["run", "--directory", "/path/to/OMOS-MCP", "omos-mcp"]
}
}
}
```
**3) ทดสอบ:** ถาม agent ว่า *"มีโปรเจคอะไรบ้างใน OMOS"* — ต้องได้รายชื่อโปรเจค แล้วถามต่อเจาะโปรเจคใดโปรเจคหนึ่งต้องได้ไฟล์พร้อม link
**(ทางเลือก) รันเป็น HTTP server ในเครื่อง:** เปิด `OMOS_AUTH_TOKEN` ใน `.env` แล้ว
```bash
uv run omos-mcp-http
```
endpoint อยู่ที่ `http://localhost:8000/mcp` เชื่อมด้วย:
```bash
claude mcp add --transport http omos http://localhost:8000/mcp -H "Authorization: Bearer <OMOS_AUTH_TOKEN>"
```
## รับ Transcript อัตโนมัติ (`POST /transcripts`)
สำหรับให้ระบบอื่น (เช่น **น้องจิก**) ส่ง transcript หลังประชุมจบ แล้ว OMOS หาโฟลเดอร์โปรเจคให้เอง บันทึกเป็น **Google Doc** ใน `<Project>/Meeting Transcripts/`
```bash
curl -X POST https://<app>.onrender.com/transcripts \
-H "Authorization: Bearer $OMOS_INGEST_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"project": "To Do List",
"title": "Weekly sync",
"date": "2026-09-03",
"transcript": "...",
"meeting_id": "zoom-8891",
"translation": "..."
}'
```
| field | บังคับ | หมายเหตุ |
|---|---|---|
| `project` | ✅ | ต้องตรงกับชื่อโฟลเดอร์ชั้นบนสุด **เป๊ะๆ** — ไม่มี fuzzy match |
| `title` / `date` | ✅ | ใช้ตั้งชื่อไฟล์: `2026-09-03 — Weekly sync` |
| `transcript` | ✅ | สูงสุด 1 MB (~350,000 ตัวอักษรไทย) |
| `meeting_id` | ➖ | **ควรส่ง** — ใช้กันไฟล์ซ้ำตอน retry |
| `translation` | ➖ | ต่อท้ายเป็นอีก section ในไฟล์เดียวกัน |
ตอบกลับ `{"status": "created"|"exists", "file_id", "link", "folder"}`
| HTTP | ความหมาย | `retryable` |
|---|---|---|
| `200` | บันทึกแล้ว (`created`) หรือเคยบันทึกไว้แล้ว (`exists`) | — |
| `400` | ข้อมูลไม่ครบ / ใหญ่เกิน | `false` |
| `401` | token ผิด | `false` |
| `403` | service account ไม่มีสิทธิ์เขียน | `false` |
| `404` | ไม่รู้จักชื่อโปรเจค | `false` |
| `409` | ชื่อโปรเจคซ้ำ ตัดสินไม่ได้ | `false` |
| `429` | ชน rate limit ของ Google | `true` |
| `502` | Drive ล่มชั่วคราว | `true` |
| `507` | Drive เต็ม / ปลายทางไม่ใช่ Shared Drive | `false` |
ทุก error body มี field `retryable` บอกตรงๆ ว่า retry แล้วมีโอกาสสำเร็จไหม — ผู้เรียกไม่ต้องจำ status code
📘 **สเปกเต็มสำหรับทีมที่จะเรียก API นี้: [docs/API.md](docs/API.md)**
> **ความปลอดภัย:** endpoint นี้เขียนไฟล์ได้ ต้องตั้ง `OMOS_INGEST_TOKEN` ถึงจะทำงาน (ไม่ตั้ง = ไม่มี route นี้อยู่เลย ตอบ 404 และ scope ของ service account เป็น `drive.readonly` เขียนอะไรไม่ได้) และโค้ด**สร้างไฟล์ใหม่อย่างเดียว ไม่มี update/delete** เขียนได้เฉพาะในโฟลเดอร์โปรเจคที่ resolve ได้แล้วเท่านั้น
>
> ⚠️ ฟีเจอร์นี้ต้องให้ service account มีสิทธิ์ **Content manager** (เขียนได้) บน Shared Drive และ scope จะเปลี่ยนเป็น `drive` อัตโนมัติเมื่อตั้ง token — ถ้าใช้แค่ฝั่งอ่าน ไม่ต้องตั้ง `OMOS_INGEST_TOKEN` ก็ได้
>
> **ลำดับสำคัญ:** ให้สิทธิ์ Content manager **ก่อน** แล้วค่อยตั้ง token — ถ้าสลับกัน transcript ที่ส่งเข้ามาช่วงนั้นจะได้ `403` พร้อม `retryable: false` ผู้เรียกจะไม่ส่งซ้ำ ข้อมูลหายถาวร
## เรื่อง timeout
Drive ใหญ่ + Render free tier ทำให้ tool call ถูก client ตัดได้ ฝั่ง server จัดการให้แล้ว: ทุก tool มี **deadline 20 วินาที** ถ้าไม่ทันจะ**คืนผลเท่าที่ได้พร้อมคำเตือนว่ายังไม่ครบ** (ไม่ใช่ error เปล่าๆ) ปรับได้ที่ `OMOS_DEADLINE`
สองอย่างที่ต้องทำเองถ้ายังเจอ timeout:
**1) ขยาย timeout ฝั่ง client** — Claude Code ตั้งได้ (หน่วยมิลลิวินาที):
```bash
claude mcp add --transport http omos https://<app>.onrender.com/mcp -e MCP_TOOL_TIMEOUT=120000
```
> Claude.ai เว็บ / Cowork ตั้งค่านี้ไม่ได้ — ต้องพึ่ง deadline ฝั่ง server อย่างเดียว
**2) กัน Render หลับ** — free tier จะ spin down เมื่อไม่มีคนใช้ request แรกหลังหลับจะช้า 50 วินาทีขึ้นไป (มักโดน timeout พอดี) แก้ได้ 2 ทาง:
- ตั้ง cron ฟรี (เช่น [cron-job.org](https://cron-job.org)) ยิง `https://<app>.onrender.com/healthz` ทุก 10 นาที
- หรืออัพเป็น Render Starter (~$7/เดือน) แล้วไม่หลับเลย
## Deploy เป็น MCP link (Render)
1. Push repo นี้ขึ้น GitHub (มี `Dockerfile` + `render.yaml` ให้แล้ว)
2. Render → **New → Blueprint** → เลือก repo นี้
3. ตั้ง env ใน dashboard:
- `OMOS_ROOT_FOLDER_ID` = folder id ของ root OMOS
- `GOOGLE_SERVICE_ACCOUNT_JSON` = เนื้อไฟล์ key JSON ทั้งก้อน (paste ตรงๆ)
- `OMOS_AUTH_TOKEN` Render สุ่มให้เอง → ก็อปไปแจกทีม
4. Deploy เสร็จ → endpoint คือ `https://<app>.onrender.com/mcp`
ผู้ใช้เชื่อมต่อ:
```bash
claude mcp add --transport http omos https://<app>.onrender.com/mcp \
-H "Authorization: Bearer <OMOS_AUTH_TOKEN>"
```
Cursor / VS Code: ใส่ URL + header `Authorization: Bearer <token>` ใน MCP config
### ให้ Claude.ai / ChatGPT (เว็บ) ใช้ — OAuth
เว็บ client ไม่มีช่องใส่ header ต้องใช้ OAuth ผ่าน provider เช่น **WorkOS AuthKit** (ฟรีถึง 1M users):
1. สมัคร [workos.com](https://workos.com) → เปิดใช้ AuthKit → ตั้งวิธี login + จำกัดเฉพาะคนในทีม
2. Dashboard → Connect → Configuration → เปิด **Client ID Metadata Document** และ **Dynamic Client Registration**
3. เพิ่ม **Resource Indicator** = `https://<app>.onrender.com` (ไม่มี `/mcp`)
4. ตั้ง env บน Render เพิ่ม 3 ตัว: `OAUTH_ISSUER` = AuthKit domain, `OAUTH_AUDIENCE` = `https://<app>.onrender.com`, `PUBLIC_URL` = `https://<app>.onrender.com`
5. ผู้ใช้: Claude.ai → Settings → Connectors → Add custom connector → ใส่ `https://<app>.onrender.com/mcp` → login ผ่าน WorkOS
> โหมด OAuth กับ bearer token อยู่ด้วยกันได้ — เว็บใช้ OAuth, CLI ยังใช้ token ได้
## ทดสอบ (converters + thread safety + การอ่านแบบ lazy)
```bash
uv run python test_convert.py
uv run python test_ingest.py
```
TDQS
Scored across 4 tools
Each tool has a clearly distinct purpose: omos_index provides the full index, omos_search performs full-text search with optional filters, omos_refresh rebuilds the index, and omos_read reads file content. There is no overlap or ambiguity between them.
All tools follow a consistent 'omos_<verb>' pattern (index, search, refresh, read). The naming is predictable and uniform, making it easy for an agent to understand the action each tool performs.
With 4 tools, the set is well-scoped for a drive retrieval server. Each tool addresses a necessary function (listing, searching, refreshing, reading) without being too few or excessive. The count matches the domain's core operations.
The tool surface covers the full read lifecycle for the OMOS drive: obtaining the index, searching, refreshing for updates, and reading file content. No obvious gaps exist for a retrieval-focused server; all necessary operations are present.