feat: openclaw-egp plugin v0.1.0

OpenClaw plugin for RAG over Vietnamese government procurement documents.
Ships:
- openclaw.plugin.json manifest (id=openclaw-egp, configSchema requires serverUrl)
- 5 SKILL.md guides (Vietnamese): search-procurement-docs, list-entities,
  describe-entity, fetch-raw-file, entity-types-glossary
- README with install/configure/uninstall instructions
- MIT license

Companion server: openclaw-egp-server (provides /mcp + /files/{id}/raw).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
main v0.1.0
VietND 1 month ago
commit b49f2062ce

5
.gitignore vendored

@ -0,0 +1,5 @@
.DS_Store
*.swp
.vscode/
.idea/
*.log

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 VietND / pkhtech
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,79 @@
# OpenClaw EGP Plugin
RAG over Vietnamese government procurement documents (TBMT, KHLCNT, KQLCNT). Connects an OpenClaw agent to a running [`openclaw-egp-server`](https://git.pkhtech.com/vietnd11/openclaw-egp-server) instance via remote MCP.
## Install
```bash
openclaw plugins install git:git.pkhtech.com/vietnd11/openclaw-egp-plugin@v0.1.0
```
## Configure
Point the plugin at your `openclaw-egp-server` instance:
```bash
openclaw plugins config openclaw-egp set serverUrl=http://100.123.9.15:8000
openclaw plugins restart
```
The plugin reads `serverUrl` from its config; the agent connects to `{serverUrl}/mcp` for the 5 MCP tools below.
> **Note on MCP wiring.** OpenClaw's manifest schema does not currently declare a dedicated field for remote MCP server URLs. If `openclaw plugins inspect openclaw-egp --runtime` does not list the 5 tools after install, you may need to add an entry to the OpenClaw global config pointing the MCP runtime at `{serverUrl}/mcp`. See the [server repo's `/mcp` endpoint](https://git.pkhtech.com/vietnd11/openclaw-egp-server#mcp-endpoint) for the canonical URL.
## Capabilities
| Tool | Purpose |
|------|---------|
| `search_procurement_docs(query, limit?, entity_type?, entity_code?, mode?)` | Semantic search over the corpus |
| `list_entities(entity_type?, status?, limit?)` | Enumerate TBMT/KHLCNT/KQLCNT records |
| `describe_entity(entity_code)` | Metadata + file list for one entity |
| `fetch_raw_file_url(file_id)` | Public download URL for the original file |
| `entity_types_glossary()` | Static domain reference |
Each tool has an accompanying `SKILL.md` in `skills/` telling the agent **when** to use it. Skills are loaded automatically when the plugin is installed.
## Server requirements
The plugin depends on an `openclaw-egp-server` instance with:
- `/mcp` endpoint mounted (FastMCP Streamable HTTP transport).
- `/files/{id}/raw` endpoint for raw file download.
- `PUBLIC_BASE_URL` env var set to the externally-reachable URL (required for `fetch_raw_file_url`).
See the [server README](https://git.pkhtech.com/vietnd11/openclaw-egp-server#mcp-endpoint) for setup.
## Verify install
```bash
openclaw plugins inspect openclaw-egp --runtime
```
Expected: `openclaw-egp` enabled, 5 skills loaded, `serverUrl` shown as configured.
Then in chat, try:
> "Liệt kê các TBMT đã indexed"
The agent should call `list_entities(entity_type="TBMT", status="indexed")` and return a markdown table.
## Update
```bash
openclaw plugins install git:git.pkhtech.com/vietnd11/openclaw-egp-plugin@v0.2.0
openclaw plugins restart
```
## Uninstall
```bash
openclaw plugins uninstall openclaw-egp
```
## Server repository
[openclaw-egp-server](https://git.pkhtech.com/vietnd11/openclaw-egp-server) — FastAPI app exposing `/mcp` (5 tools), `/files/{id}/raw`, plus the admin dashboard and ingestion pipeline.
## License
MIT — see [LICENSE](LICENSE).

@ -0,0 +1,39 @@
{
"id": "openclaw-egp",
"name": "OpenClaw EGP",
"description": "RAG over Vietnamese government procurement documents (TBMT, KHLCNT, KQLCNT). Connects an OpenClaw agent to a running openclaw-egp-server instance via remote MCP.",
"version": "0.1.0",
"configSchema": {
"type": "object",
"required": ["serverUrl"],
"properties": {
"serverUrl": {
"type": "string",
"format": "uri",
"description": "Base URL of the openclaw-egp-server instance (no trailing slash)"
}
}
},
"uiHints": {
"serverUrl": {
"label": "EGP Server URL",
"placeholder": "http://100.123.9.15:8000"
}
},
"skills": [
"skills/search-procurement-docs",
"skills/list-entities",
"skills/describe-entity",
"skills/fetch-raw-file",
"skills/entity-types-glossary"
],
"contracts": {
"tools": [
"search_procurement_docs",
"list_entities",
"describe_entity",
"fetch_raw_file_url",
"entity_types_glossary"
]
}
}

@ -0,0 +1,44 @@
---
name: describe-entity
description: Xem chi tiết một hồ sơ theo entity_code — metadata, danh sách files, trạng thái index. Dùng khi user hỏi sâu về một record cụ thể.
---
# Describe Entity
## Khi nào dùng
- "Cho tôi chi tiết hồ sơ TBMT-2026-001"
- "Hồ sơ X có bao nhiêu file?"
- "Trạng thái index của KHLCNT-... ?"
- "File chính của hồ sơ này tên gì?"
## Workflow
1. Cần `entity_code` chính xác. Nếu user nói mơ hồ ("hồ sơ A", "cái gói thầu hôm qua"):
- Hỏi lại để clarify, hoặc
- Dùng `list_entities` để tìm trước rồi xác nhận với user.
2. Gọi `describe_entity(entity_code="<mã>")`.
3. Xử lý lỗi:
- Tool raise `entity_not_found` → thông báo user "Không tìm thấy hồ sơ <mã>", gợi ý dùng `list_entities` để xem danh sách.
4. Nếu OK: trả lời gồm:
- Metadata: type, status, ngày tạo / cập nhật.
- Bảng files: mỗi row có `id` (cần cho `fetch-raw-file`), `file_name`, `file_type`, `size`.
## Ví dụ
**User:** "Chi tiết hồ sơ TBMT-2026-001"
**Agent:**
```
→ describe_entity(entity_code="TBMT-2026-001")
← {entity: {...}, files: [{id: 10, file_name: "main.pdf", ...}, ...]}
```
**Reply:** metadata + bảng files với cột `id` để user/agent có thể tham chiếu khi cần download.
## Anti-patterns
- ❌ Đừng đoán entity_code — phải hỏi nếu user mơ hồ.
- ❌ Đừng tóm tắt nội dung file dựa vào file_name — đó là việc của `search_procurement_docs`.
- ❌ Đừng giấu `id` của file — agent/user cần `id` để tải file gốc qua `fetch_raw_file_url`.

@ -0,0 +1,41 @@
---
name: entity-types-glossary
description: Giải thích thuật ngữ nghiệp vụ đấu thầu — TBMT, KHLCNT, KQLCNT, Chương, Mẫu số. Dùng khi user hỏi định nghĩa, không phải nội dung cụ thể.
---
# Entity Types Glossary
## Khi nào dùng
Câu hỏi về **định nghĩa / thuật ngữ**, không phải dữ liệu cụ thể:
- "TBMT là gì?"
- "Chương II là cái gì trong hồ sơ?"
- "Khác nhau giữa Mẫu số 01 và 01B?"
- "Cấu trúc 1 hồ sơ đấu thầu thường gồm gì?"
- "Hybrid sections là gì?"
## Workflow
1. Gọi `entity_types_glossary()` — không tham số.
2. Đọc markdown trả về, trích **phần liên quan** đến câu hỏi của user.
3. Trả lời ngắn gọn, paraphrase nếu cần. Có thể chèn ví dụ nếu user là người mới.
## Ví dụ
**User:** "TBMT là gì?"
**Agent:**
```
→ entity_types_glossary()
← "# Vietnamese Government Procurement Entity Types\n\n## Entity types\n\n- **TBMT** (Thông báo mời thầu) — Invitation to Bid notice\n..."
```
**Reply:** "**TBMT** = Thông báo mời thầu — văn bản mời các nhà thầu tham gia đấu thầu một gói thầu cụ thể. Trong hệ thống, mỗi TBMT là một record có mã duy nhất, chứa các file đính kèm (PDF/DOCX/XLSX) mô tả yêu cầu kỹ thuật, dữ liệu gói thầu, các mẫu biểu."
## Anti-patterns
- ❌ Đừng dùng skill này cho câu hỏi về **nội dung cụ thể** trong hồ sơ — dùng `search-procurement-docs`.
- ❌ Đừng dump toàn bộ glossary mỗi lần — chỉ phần liên quan đến câu hỏi.
- ❌ Đừng gọi tool nhiều lần — content không đổi, cache lại trong context conversation.

@ -0,0 +1,42 @@
---
name: fetch-raw-file
description: Lấy URL download file PDF/XLSX/DOCX gốc theo file_id. Dùng khi user muốn xem hoặc tải file nguồn để kiểm tra nội dung.
---
# Fetch Raw File URL
## Khi nào dùng
- "Cho tôi xem file gốc của tài liệu X"
- "Tôi muốn download PDF này"
- "Link tải file gốc?"
- "Mở file để tôi check lại"
## Workflow
1. Cần `file_id` (integer). Cách lấy:
- Từ kết quả `describe_entity` (mỗi file có `id`).
- Từ payload của `search_procurement_docs` (chứa file metadata).
2. Gọi `fetch_raw_file_url(file_id=<id>)`.
3. Xử lý lỗi:
- `public_base_url not configured` → "Server chưa config public URL, liên hệ admin."
- `file_not_found` → "Không tìm thấy file_id `<id>`, vui lòng kiểm tra lại."
4. Nếu OK: trả URL dạng **clickable link** kèm `filename``content_type` để user biết file gì trước khi click.
## Ví dụ
**Agent:**
```
→ fetch_raw_file_url(file_id=42)
← {url: "http://100.123.9.15:8000/files/42/raw", filename: "tender.pdf", content_type: "application/pdf"}
```
**Reply:** "📄 [tender.pdf](http://100.123.9.15:8000/files/42/raw) — PDF, click để download."
## Anti-patterns
- ❌ Đừng tự bịa URL — phải gọi tool để lấy URL đúng (URL phụ thuộc cấu hình `PUBLIC_BASE_URL` của server, không cố định).
- ❌ Đừng giấu URL — luôn show để user click được.
- ❌ Đừng gọi tool này nếu user chỉ hỏi nội dung — dùng `search_procurement_docs` thay vì.
- ❌ Đừng đoán `file_id` — phải có từ `describe_entity` hoặc search results trước.

@ -0,0 +1,40 @@
---
name: list-entities
description: Liệt kê các hồ sơ (TBMT/KHLCNT/KQLCNT) có trong kho dữ liệu. Dùng khi user muốn tổng quan "có những tài liệu nào" hoặc đếm số lượng theo trạng thái.
---
# List Entities
## Khi nào dùng
- "Có những hồ sơ nào trong hệ thống?"
- "Liệt kê các TBMT đã upload"
- "Bao nhiêu KHLCNT đang ở trạng thái indexed?"
- "Cho tôi xem 10 record mới nhất"
## Workflow
1. Gọi `list_entities()` — không tham số nếu user hỏi chung.
2. Lọc theo `entity_type` nếu user nêu loại cụ thể (`"TBMT"` / `"KHLCNT"` / `"KQLCNT"`).
3. Lọc theo `status` nếu user nêu trạng thái (`"indexed"`, `"uploaded"`, `"failed"`).
4. Mặc định `limit=50` — đủ cho hầu hết câu hỏi.
5. Trả lời dạng bảng markdown với các cột: `Code`, `Type`, `Status`, `Updated`.
## Ví dụ
**User:** "Cho tôi xem danh sách TBMT đã indexed"
**Agent:**
```
→ list_entities(entity_type="TBMT", status="indexed")
← [{entity_code: "TBMT-2026-001", ...}, ...]
```
**Reply:** bảng markdown gồm các record + tổng số.
## Anti-patterns
- ❌ Đừng dùng `list_entities` rồi tự duyệt trả lời nội dung — đó là việc của `search_procurement_docs`.
- ❌ Đừng dump toàn bộ raw row cho user — chỉ surface các trường có ý nghĩa: code, type, status, updated.
- ❌ Đừng tăng `limit` lên rất lớn (>200) — gây nhiễu cho user. Nếu cần xem nhiều hơn, page bằng cách lọc theo `entity_type`.

@ -0,0 +1,47 @@
---
name: search-procurement-docs
description: Tìm kiếm trong kho hồ sơ đấu thầu (TBMT, KHLCNT, KQLCNT) bằng câu hỏi tự nhiên. Dùng khi user hỏi về nội dung cụ thể có trong file PDF đã upload — yêu cầu năng lực, tiêu chí đánh giá, dữ liệu gói thầu.
---
# Search Procurement Docs
## Khi nào dùng
Khi user hỏi câu cụ thể đòi nội dung từ hồ sơ — ví dụ:
- "Yêu cầu kinh nghiệm trong gói thầu X là gì?"
- "Tiêu chí đánh giá năng lực tài chính trong TBMT-2026-001?"
- "Phạm vi cung cấp hàng hoá của KHLCNT-...?"
## Workflow
1. Gọi tool `search_procurement_docs(query=<câu hỏi nguyên văn>)`.
- Nếu user nêu rõ mã hồ sơ → thêm `entity_code="<mã>"`.
- Nếu user nêu loại hồ sơ → thêm `entity_type="TBMT"` / `"KHLCNT"` / `"KQLCNT"`.
- Mặc định `limit=5`; nâng lên 10 khi user muốn tổng hợp rộng.
2. **Đọc `limitations` trước**:
- `"collection_missing"` → trả lời: "Hệ thống chưa khởi tạo collection, vui lòng admin cấu hình trước."
- `"empty_index"` → "Chưa có dữ liệu nào trong kho, admin cần upload và index trước."
- `"results_contain_boilerplate_candidates"` → cảnh báo có thể có template, không phải dữ liệu thật.
3. Nếu có results: trả lời tổng hợp + **trích nguồn** kèm `entity_code`, `source_file`, `page`.
4. Nếu user muốn xem file gốc → chuyển sang skill `fetch-raw-file`.
## Ví dụ
**User:** "Yêu cầu hợp đồng tương tự trong gói KHLCNT-2026-001 là gì?"
**Agent:**
```
→ search_procurement_docs(query="yêu cầu hợp đồng tương tự", entity_code="KHLCNT-2026-001")
← {results: [...], limitations: []}
```
**Reply:** "Theo hồ sơ KHLCNT-2026-001, yêu cầu hợp đồng tương tự gồm: [tóm tắt]. Trích nguồn: [KHLCNT-2026-001, Chương III, page 12]."
## Anti-patterns
- ❌ Đừng gọi tool nhiều lần với cùng query — phí token.
- ❌ Đừng trả lời không có cite — user luôn cần kiểm tra nguồn.
- ❌ Đừng dùng tool này cho câu hỏi định nghĩa thuật ngữ — dùng `entity-types-glossary`.
- ❌ Đừng đoán nội dung — chỉ trả lời theo `results`. Nếu không có hit, nói "không tìm thấy".