Metadata-Version: 2.4
Name: mcp-gac-ex
Version: 1.0.2
Summary: MCP stdio server: GAC workflow tools (calls REST API with Bearer token)
License: MIT
Keywords: automation,cursor,gac,mcp,workflow
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Requires-Python: >=3.11
Requires-Dist: httpx>=0.28.0
Requires-Dist: mcp>=1.0.0
Description-Content-Type: text/markdown

# GAC Workflow MCP (stdio)

MCP gọi REST với `Authorization: Bearer <token>`. Mặc định mới: dùng **PAT** (`GAC_PAT`) để gọi BE exchange sang JWT ngắn hạn (`/auth/pat/exchange`), sau đó MCP tự dùng JWT đó để call API.

Fallback vẫn hỗ trợ **JWT trực tiếp** (`GAC_JWT` / `GAC_TOKEN`) để tương thích ngược.

**Auth trước khi bật MCP:** trước `mcp.run()`, process gọi `GET /me` với JWT. Nếu **401/403** → thoát mã 2, Cursor báo MCP lỗi và **không** expose tools/prompts (vì chưa vào vòng lặp stdio).

**Lưu ý:** Cookie session của trình duyệt **không** được gửi sang MCP. Chỉ Bearer. Tool **`list_projects`** gọi `GET /projects` (project user được phép xem). Các tool workflow dùng JWT là đủ. Tool `execute_workflow` phụ thuộc endpoint execute (hiện ưu tiên session) — có thể bỏ qua hoặc bật thử bằng `GAC_MCP_ENABLE_EXECUTE=true`.

## CLI — giống tool MCP `list_workflows`

Sau khi `export GAC_BASE_URL` và `GAC_PAT` (hoặc `GAC_JWT`):

```bash
cd examples/gac_mcp_workflow
uv run python scripts/list_workflows.py 1
```

(`1` = `project_id`; hoặc chỉ `uv run python scripts/list_workflows.py` nếu đã set `GAC_DEFAULT_PROJECT_ID`.)

## PAT/JWT auth cho MCP

Ưu tiên đặt **`GAC_PAT`** trong `env` của MCP. MCP sẽ gọi endpoint exchange để lấy access token JWT runtime.

Nếu chưa có PAT hoặc chưa bật exchange endpoint, dùng **`GAC_JWT`** (hoặc `GAC_TOKEN`) như trước.

**Tuỳ chọn:** `GAC_JWT_FILE` = đường dẫn file chỉ chứa JWT (khi không muốn lưu token trong JSON). Không bắt buộc; nếu không set thì chỉ cần `GAC_JWT` như trên.

Với PAT mode, MCP tự refresh JWT trước khi hết hạn. Với JWT direct mode, khi token hết hạn cần cập nhật lại `GAC_JWT`.

## Workflow CRUD tools (focus)

MCP hiện tập trung mạnh vào CRUD cho workflow:

- `list_workflows`
- `get_workflow`
- `create_workflow`
- `update_workflow`
- `delete_workflow`
- `publish_workflow`
- `list_workflow_versions`
- `get_workflow_version`
- `delete_workflow_version`
- `restore_workflow_version`

Khuyến nghị thứ tự thao tác:
1. `list_workflows` để kiểm tra trùng tên.
2. `create_workflow` với graph tối thiểu hợp lệ.
3. `get_workflow` để review draft.
4. `update_workflow` khi cần chỉnh node/edge.
5. `delete_workflow` chỉ sau khi xác nhận.

## Cách prompt để agent tạo workflow đúng

Khi prompt user, nên ghi rõ:
- Mục tiêu workflow.
- Tổng số node.
- Mỗi node gồm: `id`, `type`, chức năng.
- Mỗi edge gồm: `source -> target`.
- Nếu có branch thì ghi rõ nhánh điều kiện.

Prompt mẫu (VN):

```text
Tạo workflow gồm 4 node:
1) start-1 (start): điểm bắt đầu
2) agent-1 (agent): tóm tắt nội dung user
3) agent-2 (agent): chuyển summary thành checklist
4) end-1 (end): kết thúc

Edges:
- e1: start-1 -> agent-1
- e2: agent-1 -> agent-2
- e3: agent-2 -> end-1

Hãy tạo nodes_json và edges_json hợp lệ để gọi create_workflow.
```

Prompt mẫu (EN):

```text
Create a workflow with 3 nodes:
- start-1 (start): entry
- agent-1 (agent): classify incoming request into categories
- end-1 (end): finish

Edges:
- e1: start-1 -> agent-1
- e2: agent-1 -> end-1

Return valid nodes_json and edges_json for create_workflow.
```

### Checklist trước khi gọi `create_workflow` / `update_workflow`

- Có đúng 1 node `start`.
- Có ít nhất 1 node `end`.
- Mọi `edge.source` và `edge.target` đều tồn tại trong danh sách node.
- Không có node quan trọng bị disconnected.
- `nodes_json` và `edges_json` là JSON hợp lệ (parse được).

### MCP preflight/normalize (mới) — tham chiếu DSL GAC FE

Payload `POST/PUT` giống console: `name`, `description?`, `nodes[]`, `edges[]`. Để gần với **Next.js + React Flow** (sanitize trước khi lưu), MCP tự bổ sung/ chuẩn hóa:

| Thành phần | Hành vi MCP |
|------------|-------------|
| **Edges** | Nếu thiếu `type`, gán **`"status"`** (StatusEdge như UI). |
| **Agent** | Thiếu `data.sourceType` → `{"type":"text"}`. |
| **if-else** | Map `else` → **`output-else`**; `condition-<id>` → **`output-<id>`**; sinh **`dynamicSourceHandles`** từ `conditions` khi UI field chưa có; nếu chỉ có **`dynamicSourceHandles`** (bản export FE) thì mirror sang **`conditions`** cho BE. |
| **Thực thi (CEL)** | Trên BE, biến **`input.<field>`** (khớp validator FE) và **`input.params.<field>`** đều trỏ cùng payload node trước; ưu tiên viết điều kiện dạng **`input.<field>`** cho if-else. |
| **data-extractor** | Không tự sửa: khi nguồn project, FE thường lưu `selectedFilePath: null` (path động) — cần nắm khi copy graph từ UI. |

Graph cơ bản: đúng 1 `start`, ≥1 `end`, mọi `source`/`target` tồn tại. Preflight với `update_workflow` chỉ chạy khi gửi **cả** `nodes_json` và `edges_json` trong cùng lần gọi.

## Chạy tay

```bash
cd examples/gac_mcp_workflow
uv sync
export GAC_BASE_URL="http://localhost:8000/api/v1"
export GAC_PAT="gac_pat_<id>_<secret>"
# hoặc fallback:
# export GAC_JWT="<access_token từ login>"
# export GAC_DEFAULT_PROJECT_ID="1"   # tùy chọn
uv run python -m gac_mcp
```

## Lỗi `No module named gac_mcp` trong Cursor

Cursor đôi khi **không áp dụng `cwd`** khi spawn MCP → `uv run python -m gac_mcp` chạy nhầm context và gọi Python hệ thống (vd. 3.10) không có package.

**Cách xử lý:** dùng `uv run --directory <đường_dẫn_tuyệt_đối_tới_examples/gac_mcp_workflow> ...` trong **`args`** (xem `mcp.json.example`). Không phụ thuộc `cwd`.

Trong thư mục package chạy một lần: `uv sync`.

---

## Lệnh trong `mcp.json` có chắc chạy local không?

**Có**, nếu đủ điều kiện sau:

| Điều kiện | Ghi chú |
|-----------|---------|
| `uv` có trên `PATH` | Cài: https://docs.astral.sh/uv/ (`brew install uv` hoặc script). |
| Project context cho `uv` | Dùng `uv run --directory /path/to/examples/gac_mcp_workflow` trong **`args`** (khuyến nghị). Chỉ dùng `cwd` là không đủ với một số bản Cursor. |
| Đã `uv sync` ít nhất một lần trong thư mục đó | Tạo `.venv` và cài `mcp`, `httpx`. Không sync vẫn có thể chạy nhưng `uv run` sẽ resolve deps chậm / lỗi. |

`uv run --directory … python -m gac_mcp` = chạy module trong đúng project — ổn định hơn `cwd` một mình.

## Cursor — copy `mcp.json.example`

1. Copy nội dung vào **Cursor → MCP** hoặc **`.cursor/mcp.json`** (tùy bản Cursor).
2. Trong **`args`**, sửa đường dẫn sau `--directory` thành thư mục `examples/gac_mcp_workflow` trên máy bạn (path tuyệt đối).
3. Đặt `GAC_PAT` (hoặc `GAC_JWT`) và `GAC_BASE_URL`.

File mẫu `mcp.json.example` đã dùng `--directory` + path workspace dev; máy khác chỉ cần sửa segment path trong `args`.

## `uv` là gì? (tại sao `uv build`, `uvx`, `uv run`?)

**[uv](https://docs.astral.sh/uv/)** là công cụ dòng lệnh (một file binary) của Astral dùng để quản lý **Python + package + virtualenv**: cài nhanh, tương thích `pyproject.toml` / `pip`, thường dùng thay cho hoặc cùng với `pip` + `venv` trong project hiện đại.

| Lệnh (gợi ý) | Ý nghĩa |
|--------------|---------|
| `uv sync` | Đồng bộ dependency theo `pyproject.toml` / lock, tạo `.venv` trong project. |
| `uv run …` | Chạy lệnh trong môi trường project (vd. `uv run python -m gac_mcp`). |
| `uv build` | Đóng gói wheel + sdist để **publish** (lên PyPI / index nội bộ). |
| `uv publish` | Đẩy bản build lên registry (cần cấu hình token PyPI / index). |
| **`uvx`** | Tải và chạy **một bản công cụ từ PyPI tạm thời** (kiểu `npx`): user không cần `git clone` hay `--directory` nếu package đã publish và khai báo **console script** (ở đây là `gac-mcp` trong `pyproject.toml`). |

Khi **chưa** publish: vẫn cần `uv run --directory <path> …` từ source. Khi **đã** publish: có thể cấu hình Cursor với `command: uvx` + `args` trỏ tới tên package + entrypoint (kiểm tra bản `uv` cụ thể, ví dụ `uvx --from gac-mcp-workflow gac-mcp`).

Cài `uv`: xem [Installing uv](https://docs.astral.sh/uv/getting-started/installation/) (macOS: `brew install uv`, v.v.).

## Nhiều người dùng / chuyển giao (không bắt mọi người clone cùng path)

| Phương án | Ý tưởng | `mcp.json` |
|-----------|---------|------------|
| **A. Mỗi người clone repo** | Mỗi máy sửa path trong `uv run --directory ...` trong **`args`**. | `command` + `args` (+ `env`). |
| **B. Publish package (PyPI / private index)** | Đặt tên ví dụ `gac-mcp-workflow`, publish version; user cài một lệnh toàn cục. | `"command": "uvx"`, `"args": ["--from", "gac-mcp-workflow", "gac-mcp"]` (hoặc tương đương sau khi thử trên máy); **không cần `cwd`** nếu entrypoint resolve được. |
| **C. `pip install` / `pipx`** | `pipx install .` trong thư mục package hoặc từ wheel. | `"command": "gac-mcp"` hoặc `"python"`, `"-m", "gac_mcp"` tùy cách cài. |
| **D. Docker** | Image có sẵn `uv` + code; chạy container với env. | `command`/`args` trỏ vào script trong image; phức tạp hơn với Cursor (cần mount / network). |

POC hiện tại là **phương án A** (rõ ràng, không cần registry). Khi ổn định, **B** giúp mọi người dùng chung một lệnh và chỉ khác `env` (URL + JWT).

Ví dụ tối thiểu (local — **bắt buộc** `--directory` trỏ tới `examples/gac_mcp_workflow`):

```json
{
  "mcpServers": {
    "gac-workflow": {
      "command": "uv",
      "args": [
        "run",
        "--directory",
        "/Users/you/path/to/gac-gen-ai-console-be/examples/gac_mcp_workflow",
        "python",
        "-m",
        "gac_mcp"
      ],
      "env": {
        "GAC_BASE_URL": "http://localhost:8000/api/v1",
        "GAC_PAT": "gac_pat_<id>_<secret>",
        "GAC_PAT_EXCHANGE_PATH": "/auth/pat/exchange"
      }
    }
  }
}
```
