dodo-mcp 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +55 -14
  2. package/dist/cli/main.js +92 -28
  3. package/dist/cli/main.js.map +1 -1
  4. package/dist/cli/menu.js +2 -2
  5. package/dist/cli/menu.js.map +1 -1
  6. package/dist/config/globalConfig.js +6 -2
  7. package/dist/config/globalConfig.js.map +1 -1
  8. package/dist/config/tunnelConfig.js +2 -1
  9. package/dist/config/tunnelConfig.js.map +1 -1
  10. package/dist/projects/ownerAdmin.js +86 -0
  11. package/dist/projects/ownerAdmin.js.map +1 -0
  12. package/dist/projects/registry.js +90 -9
  13. package/dist/projects/registry.js.map +1 -1
  14. package/dist/security/projectAccess.js +69 -0
  15. package/dist/security/projectAccess.js.map +1 -0
  16. package/dist/security/projectAuthority.js +5 -1
  17. package/dist/security/projectAuthority.js.map +1 -1
  18. package/dist/server/appServer.js +23 -9
  19. package/dist/server/appServer.js.map +1 -1
  20. package/dist/server/bootstrap.js +5 -1
  21. package/dist/server/bootstrap.js.map +1 -1
  22. package/dist/server/configUi/app.css +8 -0
  23. package/dist/server/configUi/app.js +93 -18
  24. package/dist/server/configUi/index.html +22 -13
  25. package/dist/server/installationRuntime.js +6 -2
  26. package/dist/server/installationRuntime.js.map +1 -1
  27. package/dist/server/instructions.js +1 -1
  28. package/dist/server/instructions.js.map +1 -1
  29. package/dist/server/ipcDispatch.js.map +1 -1
  30. package/dist/server/localConfig.js +55 -13
  31. package/dist/server/localConfig.js.map +1 -1
  32. package/dist/services/ai/subagents.js +1 -1
  33. package/dist/services/ai/subagents.js.map +1 -1
  34. package/dist/services/multimodal/storage.js +10 -1
  35. package/dist/services/multimodal/storage.js.map +1 -1
  36. package/dist/services/schedules/scheduleService.js +8 -1
  37. package/dist/services/schedules/scheduleService.js.map +1 -1
  38. package/dist/store/db.js +14 -0
  39. package/dist/store/db.js.map +1 -1
  40. package/dist/tools/agentRuntimeTools.js +2 -3
  41. package/dist/tools/agentRuntimeTools.js.map +1 -1
  42. package/dist/tools/context.js +85 -5
  43. package/dist/tools/context.js.map +1 -1
  44. package/dist/tools/surface.js +2 -6
  45. package/dist/tools/surface.js.map +1 -1
  46. package/dist/tunnel/control.js +11 -4
  47. package/dist/tunnel/control.js.map +1 -1
  48. package/dist/tunnel/log.js +32 -0
  49. package/dist/tunnel/log.js.map +1 -1
  50. package/dist/tunnel/supervisor.js +103 -24
  51. package/dist/tunnel/supervisor.js.map +1 -1
  52. package/docs/ARCHITECTURE.md +12 -4
  53. package/docs/AUTH.md +6 -0
  54. package/docs/COMPATIBILITY.md +3 -2
  55. package/docs/MANUAL_ACCEPTANCE.md +22 -6
  56. package/docs/PROJECTS.md +73 -2
  57. package/docs/RELEASE_1.2.0.md +75 -0
  58. package/docs/RELEASE_NOTES.md +15 -0
  59. package/docs/SECURITY.md +27 -6
  60. package/docs/TEST_REPORT.md +74 -0
  61. package/docs/TUNNEL.md +79 -16
  62. package/docs/adr/048-persistent-connection-mode.md +3 -1
  63. package/docs/adr/050-cloudflare-local-and-project-access.md +67 -0
  64. package/docs/adr/README.md +1 -0
  65. package/package.json +2 -1
  66. package/schemas/global-config.schema.json +1 -0
  67. package/schemas/tools.compact.json +123 -3
  68. package/schemas/tools.hybrid.json +297 -3
  69. package/schemas/tools.json +828 -0
package/docs/SECURITY.md CHANGED
@@ -67,6 +67,20 @@ Launcher mode ไม่มี active AI workspace และไม่ใช้ CW
67
67
  private inert root ไม่ถูกแสดงต่อ client; OAuth และ authenticated catalog เปิดให้ตั้ง
68
68
  connection ได้ โหมดส่วนตัวซึ่งเป็นค่าเริ่มต้นอนุญาต owner-approved OAuth installation
69
69
  ให้ใช้ target ที่ owner ลงทะเบียนตาม token/profile scopes โดยไม่ต้องสร้าง ACL ซ้ำ
70
+ Simple Project Access Policy เพิ่มเพดานอีกหนึ่งชั้นต่อโปรเจกต์ (`read`/`edit`/`full` →
71
+ `dodo:read` / `+dodo:write` / `+dodo:exec`) บังคับที่จุดเดียวใน
72
+ `src/security/projectAuthority.ts` โดย intersect กับ token scopes, grant scopes และ
73
+ client ACL สิทธิ์จริงจึงเป็นส่วนที่ทับกันทั้งหมด ระดับนี้ **บีบได้อย่างเดียว ไม่เคยขยาย**:
74
+ token ที่มีแค่ `dodo:read` ยังเขียนหรือ exec ไม่ได้แม้โปรเจกต์เป็น `full` workspace ที่
75
+ ไม่ได้อยู่ใน registry (launcher root, stdio cwd) ไม่ถูกบีบเพิ่ม และค่าที่อ่านไม่ออกจะถูก
76
+ ตีเป็น `read` (fail closed) การเพิ่มโปรเจกต์กับการตั้งระดับ commit ใน transaction เดียว
77
+ จึงไม่มีโปรเจกต์ที่ลงทะเบียนครึ่งเดียว
78
+
79
+ `targetProject` (ชื่อโปรเจกต์) route เหมือน `targetProjectId` ทุกประการ: resolve ก่อน
80
+ เข้า pipeline, ค้นเฉพาะโปรเจกต์ที่ principal อ่านได้อยู่แล้ว (ไม่เปิดเผยโปรเจกต์ที่ไม่มีสิทธิ์),
81
+ ชื่อกำกวมหรือ id/ชื่อขัดกันถูกปฏิเสธ, รับเฉพาะ top level และถูกปฏิเสธใน nested args
82
+ ผ่านรายการ routing key ชุดเดียวกัน (`ROUTING_CONTEXT_KEYS`) ทุก dispatcher
83
+
70
84
  โหมด managed ต้องมี target ACL แยกตามเดิม Mutation จาก Local Config ยังผูกกับ current control context
71
85
  workspace/epoch การเลือก startup project เป็น preference เท่านั้นและไม่คัดลอก trust,
72
86
  OAuth grant, client ACL, approval, jobs หรือ workspace epoch
@@ -241,10 +255,16 @@ device-side shell สามารถเปลี่ยนข้อมูลด
241
255
  ## Cloudflare Tunnel
242
256
 
243
257
  เจ้าของเป็นผู้สร้าง remotely-managed Tunnel, hostname และ DNS DODO ไม่ใช้ Cloudflare
244
- API DODO บันทึกการเลือกแบบ exclusive เป็น `local` หรือ `tunnel` ใน private global
245
- config หากเลือก Tunnel ทุก `dodo start` จะเริ่มเฉพาะ live `cloudflared` child ของ
246
- process นั้นและหยุด child พร้อม DODO หาก credential/readiness ไม่พร้อม startup จะ
247
- fail closed และไม่ fallback ไป Local
258
+ API DODO บันทึกการเลือกแบบ exclusive หนึ่งค่าใน private global config: `local` คือ
259
+ loopback-only, `external` คือ Cloudflare Local ที่เจ้าของรัน `cloudflared` เอง และ
260
+ `tunnel` คือ DODO-owned process เฉพาะ `tunnel` เท่านั้นที่ DODO อ่าน credential,
261
+ เริ่ม child, ตรวจ readiness และหยุด child พร้อม DODO หาก credential/readiness ไม่พร้อม
262
+ startup จะ fail closed และไม่ fallback ไปโหมดอื่น
263
+
264
+ ใน `external` DODO ใช้ public HTTPS origin เป็น OAuth issuer/MCP URL แต่ไม่รับ token,
265
+ ไม่ค้น process และไม่อ้างว่า Tunnel connected จากค่า config เพียงอย่างเดียว เจ้าของ
266
+ ต้องดูแล process และ route เอง `dodo tunnel doctor` แยก public health ออกจาก process
267
+ ownership และ public health ไม่ใช่หลักฐานว่า AI client เชื่อมอยู่
248
268
 
249
269
  Token ถูกเก็บใน macOS Keychain, Windows Credential Manager หรือ Linux Secret Service
250
270
  เมื่อใช้ `--os-credential`; config เก็บ opaque locator เท่านั้น Owner-controlled
@@ -253,11 +273,12 @@ headless deployment อาจเลือก env/private-file reference โด
253
273
  browser storage หรือ setup receipt ค่า `TUNNEL_TOKEN` และ `TUNNEL_TOKEN_FILE` ถูก
254
274
  ปฏิเสธจาก environment ของ MCP jobs เสมอ
255
275
 
256
- Local Config รับ token แบบ write-only เฉพาะ POST ที่ผ่าน private capability,
276
+ Local Config รับ token แบบ write-only เฉพาะเมื่อเลือก DODO Tunnel และ POST ผ่าน private capability,
257
277
  Host/Origin/proxy checks, rate limit และ control-context headers จากนั้นส่งตรงไปยัง
258
278
  reviewed OS credential provider และล้าง request field Response/config/audit ส่งกลับ
259
279
  เฉพาะ credential presence/provider ไม่มี MCP tool สำหรับส่ง token, เปลี่ยน connection
260
- mode หรือควบคุม owner Tunnel
280
+ mode หรือควบคุม owner Tunnel การเลือก Cloudflare Local จะปิดช่อง token และ backend
281
+ ไม่รับหรืออ่าน credential สำหรับโหมดนั้น
261
282
 
262
283
  Tunnel route ต้องชี้ทุก public path ไป MCP/OAuth listener `127.0.0.1:21730` เท่านั้น
263
284
  Local Config `21731`, metrics `21732` และ private IPC ไม่ถูก expose `/config` ที่
@@ -1,5 +1,29 @@
1
1
  # DODO MCP — Test Report
2
2
 
3
+ ## Release 1.2.0 — Cloudflare Local และ Simple Project Access
4
+
5
+ ผล candidate บน macOS arm64 วันที่ 2026-09-16:
6
+
7
+ - build: **AUTOMATED_PASS** — Complete Full 138, live Full default 134, Compact 20,
8
+ Hybrid 49 และ config schemas 2 ชุด
9
+ - typecheck/lint: **AUTOMATED_PASS**
10
+ - core/integration/security/compatibility: **AUTOMATED_PASS** — 726 passed /
11
+ 35 skipped / 0 failed ใน 107 ไฟล์ที่ผ่านและ 3 ไฟล์ที่ skip
12
+ - packaging: **AUTOMATED_PASS** — 16/16 รวม release document, ADR, generated UI
13
+ assets และ fresh tarball checks ตาม manifest
14
+ - focused connection/project/OAuth/security/Chromium: **AUTOMATED_PASS** — 92 passed,
15
+ 1 skipped, 0 failed; ทดสอบสาม connection modes, write-only credential boundary,
16
+ Remote Config ผ่าน owner-managed public mode, per-project `read/edit/full`,
17
+ project-name routing และ real browser UI
18
+ - production dependency audit: **AUTOMATED_PASS** — 0 vulnerabilities
19
+ - Cloudflare Local ผ่าน public hostname/process จริงของเจ้าของ: **MANUAL_NOT_RUN**
20
+ - DODO Tunnel credential store/readiness ผ่าน public hostname จริงของ 1.2.0:
21
+ **MANUAL_NOT_RUN**
22
+
23
+ Regression รอบแรกพบ security fixture คาด marker `Project Registry` ที่หายจากหัวข้อไทย
24
+ จึงคืน marker ใน UI แล้วรัน focused และ full suite ใหม่จนผ่าน ไม่ได้ลด assertion
25
+ หรือ security requirement
26
+
3
27
  ## Release 1.1.0 — Android ADB tool family
4
28
 
5
29
  ผล candidate บน macOS วันที่ 2026-09-16:
@@ -96,6 +120,56 @@ Hybrid 49 tools; `npm run test:all` ผ่าน core/security/compatibility 99
96
120
  Live Cloudflare hostname และ OS credential store ด้วย credential ของเจ้าของสำหรับ source
97
121
  นี้: **MANUAL_NOT_RUN** — ไม่ถือว่า fixture cloudflared เป็นหลักฐานของ public network จริง
98
122
 
123
+ ## Pre-release project access, project names and tunnel readiness — 2026-09-16
124
+
125
+ หลักฐานชุดนี้สร้างก่อนรวมเข้า 1.2.0 งานรอบนี้: Simple Project Access Policy (read/edit/full),
126
+ เรียกโปรเจกต์ด้วยชื่อ (`targetProject`), แก้ข้อความที่บังคับ ACL ในโหมดส่วนตัว และแก้
127
+ Cloudflare readiness ที่สถานะกระพริบ
128
+
129
+ **รันจริงบน macOS arm64, Node v22.23.2:**
130
+
131
+ - `npm run typecheck` / `npm run lint` / `npm run build` — **AUTOMATED_PASS** (exit 0)
132
+ - `npm run test:all` — **AUTOMATED_PASS**: 705 passed / 34 skipped / 0 failed (106 ไฟล์)
133
+ + packaging 16/16
134
+ - `npx vitest run tests/security/projectAccessLevel.test.ts` (ใหม่, 11 ข้อ) —
135
+ **AUTOMATED_PASS**: mapping ระดับ→scope, `full` token ถูกบีบตามระดับ, token `read`
136
+ เขียน/exec ไม่ได้แม้โปรเจกต์ `full`, managed mode ยังต้องมี ACL แล้วระดับบีบซ้ำ,
137
+ workspace ที่ไม่ได้ลงทะเบียนไม่ถูกบีบ, ค่าที่อ่านไม่ออก → `read` (fail closed),
138
+ row เก่า migrate เป็น `full` (ไม่ถอนสิทธิ์), ระดับผิด → ไม่มีโปรเจกต์ค้าง,
139
+ ชื่อซ้ำถูกปฏิเสธแบบไม่สนตัวพิมพ์, resolve ชื่อ/ชื่อกำกวม/ชื่อไม่รู้จัก
140
+ - `npx vitest run tests/integration/projectByName.test.ts` (ใหม่) — **AUTOMATED_PASS**
141
+ ผ่าน HTTP + OAuth จริง: route ด้วยชื่อ, เขียนลงโปรเจกต์ถูกตัว, ตัวพิมพ์/ช่องว่างไม่สำคัญ,
142
+ ระดับ `edit` บล็อก exec แล้วยกเป็น `full` ทำได้, ชื่อไม่รู้จัก → NOT_FOUND,
143
+ id+ชื่อขัดกัน → INVALID_INPUT, ชื่อกำกวม → CONFLICT พร้อม candidate,
144
+ `targetProject` ใน nested args ถูกปฏิเสธและไม่มีไฟล์ถูกเขียน
145
+ - `npx vitest run tests/unit/tunnelReadiness.test.ts` (ใหม่, 8 ข้อ) — **AUTOMATED_PASS**:
146
+ probe 200/503/ปิด/ค้าง, ไม่ใช้ socket ซ้ำ (นับ connection = 2), schema รองรับทุก phase,
147
+ state.json รุ่นเก่า parse ได้, ปฏิเสธ field แปลกปลอม
148
+ - `npx vitest run tests/integration/tunnelSupervisor.test.ts` (เพิ่ม 3 ข้อ) —
149
+ **AUTOMATED_PASS** ด้วย fake cloudflared จริง: readiness ตกชั่วคราว → `degraded`
150
+ และ `connected` ยังเป็น true, ไม่มีบรรทัด "is not connected"; ล้มติดกันครบเกณฑ์ →
151
+ `disconnected` แล้ว recover กลับ `connected`; ไม่เคยรายงาน connected โดยไม่มีหลักฐาน
152
+ - `npx vitest run tests/integration/projectAccessUi.test.ts` (ใหม่, 2 ข้อ) —
153
+ **AUTOMATED_PASS** ใน Chromium จริง: เพิ่มโปรเจกต์ครบในหน้าเดียว (path/ชื่อ/ระดับ),
154
+ การ์ดแสดงระดับที่ backend เก็บจริง, เปลี่ยนระดับแล้วยืนยันหลัง backend ตอบ,
155
+ reload ยังเห็นค่าเดิม, ชื่อซ้ำขึ้น error (ไม่ขึ้น success), personal ซ่อนฟอร์ม ACL /
156
+ managed แสดงครบ, 390px ไม่มี horizontal scroll, `pageerror` = 0
157
+ - `npm run test:linux:docker` — **AUTOMATED_PASS** (node v22.23.2, arm64, Chromium)
158
+ - `npm pack` + fresh install จาก tarball — **AUTOMATED_PASS**: 461 ไฟล์, ไม่มี
159
+ state.db/.env/jwks/.sock, `dist/projects/ownerAdmin.js` และ
160
+ `dist/security/projectAccess.js` อยู่ในแพ็กเกจ, และรัน
161
+ `dodo project add --access read` → `dodo project access "auto-upload" --mode full`
162
+ → `dodo project list` ได้จริงจาก tarball ที่ติดตั้งสด
163
+ - `npm run test:windows` บน macOS — 56 passed / 30 skipped: **ไม่ใช่หลักฐาน Windows**
164
+ (ข้อที่เป็น Windows-only ถูก skip)
165
+
166
+ **MANUAL_NOT_RUN** (ยังไม่มีหลักฐาน):
167
+ - Windows native (windows-ci-02) — ยังไม่ได้รัน รอบนี้แก้สาเหตุที่เป็น Windows-specific
168
+ ไว้แล้ว (event-loop starvation จากการเขียน log, keep-alive socket) แต่ยังไม่มีหลักฐานจริง
169
+ - การยืนยันบน tunnel จริง `https://mcp.baanseriesnow.com/mcp` กับ cloudflared จริง
170
+ (เทสต์ใช้ fake cloudflared ที่ควบคุม `/ready` ได้)
171
+ - การอัปเกรดจาก state ของ 1.0.2/1.0.3 จริง (migration ตรวจด้วย fixture เท่านั้น)
172
+
99
173
  ## Scope
100
174
 
101
175
  รายงานนี้ใช้กับ DODO MCP 1.0.3 source และแยกผล automated กับ manual อย่างชัดเจน
package/docs/TUNNEL.md CHANGED
@@ -1,8 +1,11 @@
1
- # DODO MCP — Local และ Cloudflare Tunnel
1
+ # DODO MCP — Loopback, Cloudflare Local และ DODO Tunnel
2
2
 
3
3
  DODO มีโหมดเชื่อมต่อระดับ installation เพียงหนึ่งโหมดในแต่ละเวลา:
4
4
 
5
- - `local` — ใช้ MCP ที่ `http://127.0.0.1:21730/mcp` และไม่เริ่ม `cloudflared`
5
+ - `local` — **เฉพาะเครื่อง (Loopback)** ใช้ MCP ที่
6
+ `http://127.0.0.1:21730/mcp` และไม่ใช้ Cloudflare
7
+ - `external` — **Cloudflare Local (ติดตั้งในเครื่อง)** เจ้าของติดตั้งและรัน `cloudflared` เอง
8
+ DODO advertise public HTTPS origin แต่ไม่รับ token และไม่เริ่ม/หยุด process
6
9
  - `tunnel` — ใช้ public HTTPS origin เป็น MCP URL หลัก และ DODO เริ่ม/หยุด
7
10
  `cloudflared` ของตัวเองพร้อมทุก `dodo start`
8
11
 
@@ -14,7 +17,7 @@ DODO มีโหมดเชื่อมต่อระดับ installation
14
17
 
15
18
  | บริการ | ค่าเริ่มต้น | การเปิดเผย |
16
19
  |---|---:|---|
17
- | MCP + OAuth upstream | `127.0.0.1:21730` | Local เท่านั้น; Tunnel route มาที่พอร์ตนี้ |
20
+ | MCP + OAuth upstream | `127.0.0.1:21730` | bind loopback เสมอ; Cloudflare ทั้งสองโหมด route มาที่พอร์ตนี้ |
18
21
  | Local Config | `127.0.0.1:21731` | Loopback เท่านั้นเสมอ |
19
22
  | Tunnel readiness/metrics | `127.0.0.1:21732` | Loopback เท่านั้นเสมอ |
20
23
 
@@ -22,14 +25,14 @@ Cloudflare public hostname ต้อง route **ทุก path** มาที่
22
25
  `http://127.0.0.1:21730` เพื่อให้ health, OAuth discovery, authorization และ `/mcp`
23
26
  ทำงานครบ ห้าม route พอร์ต 21731, 21732, private IPC หรือ debug endpoint ออก public
24
27
 
25
- แม้โหมด Tunnel ยังต้องมี listener 21730 เป็น private upstream ให้ `cloudflared`
26
- แต่ DODO จะ advertise public HTTPS URL เป็น endpoint ที่มีผล ส่วน Local Config ยังคง
28
+ โหมด Cloudflare ทั้งสองแบบยังต้องมี listener 21730 เป็น private upstream ให้
29
+ `cloudflared` แต่ DODO จะ advertise public HTTPS URL เป็น endpoint ที่มีผล ส่วน Local Config ยังคง
27
30
  เข้าผ่าน loopback ยกเว้น bounded `/config` lease ที่เจ้าของเปิดชั่วคราวเอง
28
31
 
29
- ## เลือก Local
32
+ ## เลือกเฉพาะเครื่อง (Loopback)
30
33
 
31
34
  ```bash
32
- dodo tunnel configure --local
35
+ dodo tunnel configure --loopback
33
36
  dodo start
34
37
  ```
35
38
 
@@ -39,9 +42,30 @@ dodo start
39
42
  dodo tunnel status
40
43
  ```
41
44
 
42
- Local mode ไม่ใช้ public origin และไม่เริ่ม Tunnel หากต้องการกลับไป Tunnel ต้องเลือก
45
+ Loopback mode ไม่ใช้ public origin และไม่เริ่ม Tunnel หากต้องการกลับไป Cloudflare ต้องเลือก
43
46
  ใหม่อย่างชัดเจนด้วยคำสั่งหรือหน้า Local Config แล้ว restart DODO
44
47
 
48
+ `--local` ยังเป็น alias เดิมของ `--loopback` เพื่อไม่ทำให้สคริปต์เก่าตีความต่างไป
49
+
50
+ ## เลือก Cloudflare Local (ติดตั้งในเครื่อง)
51
+
52
+ โหมดนี้ใช้เมื่อผู้ใช้ติดตั้งและรัน `cloudflared` เอง เช่นผ่าน terminal, launchd,
53
+ systemd หรือ Windows Service DODO ไม่ขอ Tunnel token และไม่อ้างว่า process เชื่อมต่อแล้ว
54
+ เพียงเพราะบันทึก public URL
55
+
56
+ ```bash
57
+ dodo tunnel configure \
58
+ --cloudflare-local \
59
+ --public-url https://mcp.example.com
60
+
61
+ # รัน cloudflared ด้วยวิธีที่ผู้ใช้จัดการเอง แล้วจึงเปิด DODO
62
+ dodo start
63
+ ```
64
+
65
+ Cloudflare ต้อง route ทุก path ไป `http://127.0.0.1:21730` เช่นเดียวกับ DODO Tunnel
66
+ ตรวจปลายทางจริงด้วย `dodo tunnel doctor` ค่า public health ที่ผ่านยืนยันเพียงว่า origin
67
+ ตอบ DODO health ไม่ได้ยืนยันว่ามี AI client เชื่อมอยู่
68
+
45
69
  ## เลือก DODO Tunnel
46
70
 
47
71
  เจ้าของต้องสร้าง remotely-managed Tunnel, public hostname และ DNS ใน Cloudflare ก่อน
@@ -89,16 +113,17 @@ CLI argument, config JSON, log, audit, MCP response, browser storage หรื
89
113
  **การเชื่อมต่อ MCP**:
90
114
 
91
115
  1. ตั้ง Public origin เป็น HTTPS origin ของ Tunnel
92
- 2. เลือก `DODO Tunnel`
93
- 3. ใส่ Cloudflare Tunnel token หากยังไม่มี credential ที่บันทึกไว้
116
+ 2. เลือก `Cloudflare Local (ติดตั้งในเครื่อง)` หากจะรัน cloudflared เอง หรือ `DODO Tunnel` หากให้
117
+ DODO ดูแล process
118
+ 3. ใส่ Cloudflare Tunnel token เฉพาะ `DODO Tunnel`
94
119
  4. กดบันทึกและ restart DODO
95
120
 
96
- หรือเลือก `Local` แล้วบันทึกและ restart ค่าในหน้าเว็บไม่เปลี่ยน endpoint ของ process
121
+ หรือเลือก `เฉพาะเครื่อง (Loopback)` แล้วบันทึกและ restart ค่าในหน้าเว็บไม่เปลี่ยน endpoint ของ process
97
122
  ที่กำลังรันอยู่ทันที หน้าเว็บจะแสดงสถานะ restart ที่ตรงกับ runtime จริง
98
123
 
99
124
  ## Remote Config ไม่เกินหนึ่งชั่วโมง
100
125
 
101
- Remote Config ใช้ได้เฉพาะเมื่อเลือก Tunnel และ DODO-owned Tunnel กำลังรันอยู่:
126
+ Remote Config ใช้ได้เมื่อเลือก Cloudflare Local (ติดตั้งในเครื่อง) หรือ DODO Tunnel:
102
127
 
103
128
  ```bash
104
129
  dodo --web
@@ -109,6 +134,9 @@ dodo web --status
109
134
  dodo web --close
110
135
  ```
111
136
 
137
+ ในโหมด Cloudflare Local (ติดตั้งในเครื่อง) เจ้าของต้องรัน tunnel และ route ครบเอง ส่วน DODO Tunnel
138
+ จะตรวจว่า supervisor ที่ DODO เป็นเจ้าของกำลังรันก่อนเปิด lease
139
+
112
140
  `/config` ตอบ 404 ตามค่าเริ่มต้น คำสั่ง `dodo --web` เปิดหรือเปลี่ยน lease ผ่าน
113
141
  authenticated private IPC โดยไม่ restart MCP และไม่รับ Tunnel token ผ่าน IPC URL ไม่มี
114
142
  query/fragment secret Pairing code มีอายุสั้นและใช้ได้ครั้งเดียว หลังจับคู่ browser ได้
@@ -128,10 +156,45 @@ dodo tunnel logs --lines 100
128
156
  dodo tunnel stop
129
157
  ```
130
158
 
131
- `status` ไม่อ่าน credential ส่วน `doctor` ตรวจ executable, credential, local health,
132
- Tunnel readiness และ public health แยกกัน Public health ที่ผ่านพิสูจน์เพียงว่า origin
133
- ตอบ DODO health ไม่ได้พิสูจน์ว่า AI client เชื่อมต่อแล้ว `stop` ส่งสัญญาณเฉพาะ live
134
- child handle ที่ supervisor ปัจจุบันสร้าง ไม่ค้นหรือ kill process ตามชื่อ/PID เก่า
159
+ ### สถานะของ Tunnel (state machine)
160
+
161
+ `phase` ของ supervisor มีค่าเหล่านี้ และเปลี่ยนตามหลักฐานจริงเท่านั้น:
162
+
163
+ | phase | ความหมาย |
164
+ |---|---|
165
+ | `starting` | supervisor เริ่มทำงาน ยังไม่ spawn cloudflared |
166
+ | `connecting` | cloudflared รันแล้ว แต่ยัง **ไม่เคย** มีหลักฐาน readiness |
167
+ | `connected` | `/ready` ตอบ 200 — ต้องมีหลักฐานจริงเท่านั้นจึงแสดงสถานะนี้ |
168
+ | `degraded` | เคย connected แล้ว probe เริ่มล้ม แต่ยังไม่ถึงเกณฑ์ (กำลัง reconnect) |
169
+ | `disconnected` | probe ล้มติดกันครบเกณฑ์ หรือ cloudflared exit |
170
+ | `backoff` | รอ restart แบบมีขอบเขต |
171
+ | `stopping` / `stopped` / `failed` | เจ้าของสั่งหยุด / หยุดแล้ว / restart หมดโควตา |
172
+
173
+ สถานะยังมี `lastReadyAt`, `lastFailureAt` และ `consecutiveReadyFailures` เพื่อให้ดูได้ว่า
174
+ readiness สำเร็จครั้งสุดท้ายเมื่อไร
175
+
176
+ **ทำไมเมื่อก่อนสถานะกระพริบ** — เดิม probe ทุก 1 วินาทีโดย timeout 1.5 วินาที
177
+ จึงซ้อนกันได้ ผลเก่าเขียนทับผลใหม่ และ probe ที่ล้ม **ครั้งเดียว** ก็พลิกสถานะและพิมพ์
178
+ `readiness endpoint is not connected` ทันที ตอนนี้:
179
+
180
+ - probe ทำทีละครั้ง ไม่ซ้อนกัน และมี generation fence ผลจาก child เก่าถูกทิ้ง
181
+ - ต้องล้มติดกันครบเกณฑ์จึงจะเป็น `disconnected` — ล้มครั้งเดียวเป็นแค่ `degraded`
182
+ และ `connected` ยังเป็น true เพื่อไม่ให้ UI กระพริบ
183
+ - ใช้ socket ใหม่ทุกครั้ง (`agent: false`) กัน keep-alive socket ที่ถูกปิดแล้ว
184
+ กลายเป็น error ปลอม
185
+ - เขียน log ของ cloudflared แบบรวมกลุ่ม ไม่เขียนทับไฟล์ 512 KiB ทุกบรรทัด
186
+ (บน Windows การ retry rename ใช้ `Atomics.wait` ซึ่งบล็อก event loop จน probe timeout)
187
+ - cloudflared exit → `disconnected` ทันที, restart มีขอบเขตตาม `maxRestarts` เสมอ
188
+ - ไม่มี fallback เงียบ ๆ จาก tunnel ไป local และ token ไม่ปรากฏใน log หรือ error
189
+
190
+ Tunnel เป็นระดับ installation รองรับทุกโปรเจกต์ การเพิ่มหรือเลือกโปรเจกต์ไม่ restart tunnel
191
+
192
+ `status` ไม่อ่าน credential ส่วน `doctor` ตรวจ executable, local health และ public
193
+ health ทุกโหมด แต่ตรวจ credential/supervisor readiness เฉพาะ DODO Tunnel ใน
194
+ Cloudflare Local รายงาน process ownership เป็น `owner` และไม่ควบคุม process Public
195
+ health ที่ผ่านพิสูจน์เพียงว่า origin ตอบ DODO health ไม่ได้พิสูจน์ว่า AI client
196
+ เชื่อมต่อแล้ว `stop` ส่งสัญญาณเฉพาะ live child handle ที่ DODO supervisor ปัจจุบัน
197
+ สร้าง ไม่ค้นหรือ kill process ตามชื่อ/PID เก่า
135
198
 
136
199
  ## ตั้งค่า MCP client
137
200
 
@@ -2,7 +2,9 @@
2
2
 
3
3
  ## Status
4
4
 
5
- Accepted in source; unreleased.
5
+ Accepted in DODO 1.0.3. Extended by ADR-050 with an owner-managed Cloudflare Local
6
+ mode; the credential and DODO-owned supervisor rules below remain authoritative for
7
+ `tunnel` mode.
6
8
 
7
9
  ## Context
8
10
 
@@ -0,0 +1,67 @@
1
+ # ADR-050 — Cloudflare Local mode and visible per-project access
2
+
3
+ ## Status
4
+
5
+ Accepted for DODO 1.2.0. This extends ADR-048; its credential isolation and
6
+ DODO-owned supervisor rules remain in force.
7
+
8
+ ## Context
9
+
10
+ The owner UI used the label “Local” for loopback-only MCP. Owners also use the word
11
+ local for a `cloudflared` binary installed and managed on the same machine, so the
12
+ two-process ownership models were easy to confuse. The personal access UI also hid
13
+ the detailed Trust and Client ACL cards without replacing them with a visible,
14
+ simple project-level control. Authority still existed, but owners could not see or
15
+ change the intended project ceiling from the Projects page.
16
+
17
+ ## Decision
18
+
19
+ DODO exposes three mutually exclusive installation connection modes:
20
+
21
+ - `local`: loopback-only MCP; no Cloudflare process or public origin is used.
22
+ - `external`: **Cloudflare Local**; the owner installs, starts and stops
23
+ `cloudflared`. DODO advertises the reviewed public HTTPS origin but never accepts
24
+ a Tunnel token and never supervises that process.
25
+ - `tunnel`: **DODO Tunnel**; DODO reads a credential through the reviewed credential
26
+ provider, starts one owned child, waits for readiness and stops that child.
27
+
28
+ The UI never uses “Local” by itself. It labels the choices “เฉพาะเครื่อง (Loopback)”,
29
+ “Cloudflare Local (ติดตั้งในเครื่อง)” and “DODO Tunnel”. `--local` remains a
30
+ deprecated loopback alias because changing an old flag to a public mode would be an
31
+ unsafe compatibility break; new commands are `--loopback` and
32
+ `--cloudflare-local`.
33
+
34
+ Every registered project has one visible access ceiling: `read`, `edit` or `full`.
35
+ The Projects page displays and saves it in both personal and managed installations.
36
+ In personal mode this replaces repeated Trust/Client ACL configuration; in managed
37
+ mode the existing per-client ACL remains required. Effective authority is always:
38
+
39
+ ```text
40
+ token scopes ∩ live grant scopes ∩ managed ACL (when enabled) ∩ project access ceiling
41
+ ```
42
+
43
+ The ceiling cannot grant a missing OAuth scope and does not bypass workspace
44
+ identity/epoch, approval, sandbox, path/secret guards, expected hashes, idempotency
45
+ or audit.
46
+
47
+ ## Consequences
48
+
49
+ - Saving Cloudflare Local never reads, writes or validates a DODO Tunnel token.
50
+ - Remote Config can use either public Cloudflare mode; external mode depends on the
51
+ owner-managed route, while tunnel mode additionally requires the owned supervisor.
52
+ - Existing `local` config remains loopback-only. Legacy external config migrates to
53
+ the explicit `external` mode.
54
+ - Existing project rows migrate to `full` so the upgrade does not silently revoke
55
+ authority. Invalid stored levels fail closed to `read`.
56
+ - Connection-mode and project-access changes require no repository configuration and
57
+ expose no owner operation through MCP.
58
+
59
+ ## Evidence
60
+
61
+ - `tests/integration/connectionMode.test.ts`
62
+ - `tests/integration/configUiComponents.test.ts`
63
+ - `tests/security/tunnelCredentials.test.ts`
64
+ - `tests/security/remoteConfig.test.ts`
65
+ - `tests/integration/projectAccessUi.test.ts`
66
+ - `tests/integration/projectByName.test.ts`
67
+ - `tests/security/projectAccessLevel.test.ts`
@@ -65,3 +65,4 @@ decisions; ADR-011+ resolve implementation ambiguities without reducing security
65
65
  - [047 — Temporary Remote Config on the tunneled listener](047-temporary-remote-config.md)
66
66
  - [048 — Persistent exclusive Local/Tunnel connection mode](048-persistent-connection-mode.md)
67
67
  - [049 — Owner-approved Android control through ADB](049-android-adb-control.md)
68
+ - [050 — Cloudflare Local mode and visible per-project access](050-cloudflare-local-and-project-access.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dodo-mcp",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "author": "Arthittakun",
5
5
  "description": "DODO — local-first MCP server with multi-project coding, AI providers, sub-agents, context, memory, media and workflow tools; embedded OAuth and workspace permissions.",
6
6
  "type": "module",
@@ -47,6 +47,7 @@
47
47
  "docs/RELEASE_1.0.5.md",
48
48
  "docs/RELEASE_1.0.6.md",
49
49
  "docs/RELEASE_1.1.0.md",
50
+ "docs/RELEASE_1.2.0.md",
50
51
  "docs/RELEASE_NOTES.md",
51
52
  "docs/SCHEDULES.md",
52
53
  "docs/SECURITY.md",
@@ -370,6 +370,7 @@
370
370
  "type": "string",
371
371
  "enum": [
372
372
  "local",
373
+ "external",
373
374
  "tunnel"
374
375
  ]
375
376
  },