dodo-mcp 1.0.2 → 1.0.3

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 (43) hide show
  1. package/README.md +31 -18
  2. package/dist/cli/main.js +63 -66
  3. package/dist/cli/main.js.map +1 -1
  4. package/dist/cli/menu.js +9 -16
  5. package/dist/cli/menu.js.map +1 -1
  6. package/dist/config/globalConfig.js +23 -1
  7. package/dist/config/globalConfig.js.map +1 -1
  8. package/dist/config/stateImport.js +2 -2
  9. package/dist/config/stateImport.js.map +1 -1
  10. package/dist/config/tunnelConfig.js +2 -3
  11. package/dist/config/tunnelConfig.js.map +1 -1
  12. package/dist/server/appServer.js +38 -26
  13. package/dist/server/appServer.js.map +1 -1
  14. package/dist/server/configUi/app.js +35 -58
  15. package/dist/server/configUi/index.html +21 -22
  16. package/dist/server/localConfig.js +50 -63
  17. package/dist/server/localConfig.js.map +1 -1
  18. package/dist/tunnel/control.js +4 -6
  19. package/dist/tunnel/control.js.map +1 -1
  20. package/dist/tunnel/credentials.js +5 -19
  21. package/dist/tunnel/credentials.js.map +1 -1
  22. package/dist/tunnel/runtime.js +3 -4
  23. package/dist/tunnel/runtime.js.map +1 -1
  24. package/dist/tunnel/supervisor.js +8 -10
  25. package/dist/tunnel/supervisor.js.map +1 -1
  26. package/docs/AI_PROVIDERS.md +1 -1
  27. package/docs/ARCHITECTURE.md +14 -14
  28. package/docs/COMPATIBILITY.md +6 -2
  29. package/docs/MANUAL_ACCEPTANCE.md +21 -16
  30. package/docs/RELEASE_1.0.0.md +3 -0
  31. package/docs/RELEASE_1.0.3.md +57 -0
  32. package/docs/RELEASE_NOTES.md +19 -0
  33. package/docs/SECURITY.md +20 -10
  34. package/docs/TEST_REPORT.md +33 -1
  35. package/docs/TUNNEL.md +102 -80
  36. package/docs/WINDOWS.md +1 -1
  37. package/docs/adr/031-owner-selected-tunnel-supervision.md +8 -2
  38. package/docs/adr/044-global-launcher-and-cli-menu.md +3 -3
  39. package/docs/adr/047-temporary-remote-config.md +4 -4
  40. package/docs/adr/048-persistent-connection-mode.md +58 -0
  41. package/docs/adr/README.md +1 -0
  42. package/package.json +2 -1
  43. package/schemas/global-config.schema.json +6 -12
package/docs/TUNNEL.md CHANGED
@@ -1,51 +1,104 @@
1
- # DODO MCP — Tunnel Guide
1
+ # DODO MCP — Local และ Cloudflare Tunnel
2
2
 
3
- DODO เปิด MCP และ OAuth บน local loopback ผู้ใช้เป็นผู้สร้าง Cloudflare remotely-managed Tunnel, public hostname และ DNS เอง DODO supervise เฉพาะ process `cloudflared` ที่เริ่มในรอบปัจจุบันและไม่ติดตั้งเป็น system service
3
+ DODO มีโหมดเชื่อมต่อระดับ installation เพียงหนึ่งโหมดในแต่ละเวลา:
4
4
 
5
- ## Local endpoints
5
+ - `local` — ใช้ MCP ที่ `http://127.0.0.1:21730/mcp` และไม่เริ่ม `cloudflared`
6
+ - `tunnel` — ใช้ public HTTPS origin เป็น MCP URL หลัก และ DODO เริ่ม/หยุด
7
+ `cloudflared` ของตัวเองพร้อมทุก `dodo start`
6
8
 
7
- - MCP + OAuth: `http://127.0.0.1:21730`
8
- - MCP endpoint: `http://127.0.0.1:21730/mcp`
9
- - Local Config: `http://127.0.0.1:21731`
10
- - managed readiness/metrics: `http://127.0.0.1:21732` โดยค่าเริ่มต้น
9
+ การเลือกจะถูกบันทึกใน private global config และไม่มี fallback อัตโนมัติ หากเลือก
10
+ `tunnel` แล้ว credential, executable หรือ readiness ไม่พร้อม การเริ่ม DODO จะล้มเหลว
11
+ พร้อมคำอธิบาย โดยไม่เปลี่ยนไปเปิด local แทน
11
12
 
12
- Tunnel ต้อง route public hostname ทุก path ไปยัง listener `21730` เดียวกันเพื่อให้
13
- health, discovery, OAuth และ MCP ทำงานครบ ห้าม route Local Config `21731`, metrics
14
- `21732`, private IPC หรือ debug endpoint ออก public Namespace `/config` บน `21730`
15
- ตอบ 404 ตามค่าเริ่มต้นและเปิดได้ชั่วคราวเฉพาะเมื่อเจ้าของสั่งตามหัวข้อถัดไป
13
+ ## พอร์ตและขอบเขต
16
14
 
17
- ## Public origin
15
+ | บริการ | ค่าเริ่มต้น | การเปิดเผย |
16
+ |---|---:|---|
17
+ | MCP + OAuth upstream | `127.0.0.1:21730` | Local เท่านั้น; Tunnel route มาที่พอร์ตนี้ |
18
+ | Local Config | `127.0.0.1:21731` | Loopback เท่านั้นเสมอ |
19
+ | Tunnel readiness/metrics | `127.0.0.1:21732` | Loopback เท่านั้นเสมอ |
20
+
21
+ Cloudflare public hostname ต้อง route **ทุก path** มาที่
22
+ `http://127.0.0.1:21730` เพื่อให้ health, OAuth discovery, authorization และ `/mcp`
23
+ ทำงานครบ ห้าม route พอร์ต 21731, 21732, private IPC หรือ debug endpoint ออก public
24
+
25
+ แม้โหมด Tunnel ยังต้องมี listener 21730 เป็น private upstream ให้ `cloudflared`
26
+ แต่ DODO จะ advertise public HTTPS URL เป็น endpoint ที่มีผล ส่วน Local Config ยังคง
27
+ เข้าผ่าน loopback ยกเว้น bounded `/config` lease ที่เจ้าของเปิดชั่วคราวเอง
28
+
29
+ ## เลือก Local
18
30
 
19
31
  ```bash
20
- dodo init --public-url https://mcp.example.com
21
- dodo start --root /path/to/project
32
+ dodo tunnel configure --local
33
+ dodo start
22
34
  ```
23
35
 
24
- ## เปิดพร้อม DODO ด้วย token ชั่วคราว
36
+ ตรวจค่าที่ใช้อยู่:
37
+
38
+ ```bash
39
+ dodo tunnel status
40
+ ```
41
+
42
+ Local mode ไม่ใช้ public origin และไม่เริ่ม Tunnel หากต้องการกลับไป Tunnel ต้องเลือก
43
+ ใหม่อย่างชัดเจนด้วยคำสั่งหรือหน้า Local Config แล้ว restart DODO
44
+
45
+ ## เลือก DODO Tunnel
46
+
47
+ เจ้าของต้องสร้าง remotely-managed Tunnel, public hostname และ DNS ใน Cloudflare ก่อน
48
+ DODO ไม่สร้าง/ลบ Tunnel, เปลี่ยน DNS, เปิด firewall หรือติดตั้ง system service
49
+
50
+ บน macOS ตรวจหรือติดตั้ง executable ผ่าน setup:
25
51
 
26
52
  ```bash
27
53
  dodo setup --check --components cloudflared
28
- dodo init --public-url https://mcp.example.com
29
- dodo start --root /path/to/project
30
- # Cloudflare Tunnel token (temporary; Enter = local only):
54
+ dodo setup --yes --components cloudflared
31
55
  ```
32
56
 
33
- `dodo setup --yes` ใช้ component `all` เป็นค่าเริ่มต้นและรวม `cloudflared` บน macOS
34
- หรือเลือกจากเมนู `dodo --cli` ข้อ “ติดตั้ง/ตรวจ dependencies ทั้งหมด” ได้
57
+ จากนั้นเลือก Tunnel และบันทึก token ใน macOS Keychain ผ่าน hidden prompt:
58
+
59
+ ```bash
60
+ dodo tunnel configure \
61
+ --tunnel \
62
+ --public-url https://mcp.example.com \
63
+ --os-credential
64
+
65
+ dodo start
66
+ ```
35
67
 
36
- กรอก Tunnel token ที่ Cloudflare ออกให้สำหรับ remotely-managed Tunnel Token จะอยู่
37
- เฉพาะใน DODO process และ environment ของ child `cloudflared` ระหว่างรอบ เมื่อ DODO
38
- หยุด child จะหยุดตาม กด Enter โดยไม่กรอกหรือใช้คำสั่งต่อไปนี้เพื่อเปิด local MCP เท่านั้น:
68
+ Windows ใช้ Credential Manager และ Linux ใช้ Secret Service เมื่อเลือก
69
+ `--os-credential` สำหรับ headless environment สามารถใช้ owner-controlled reference:
39
70
 
40
71
  ```bash
41
- dodo start --no-tunnel
72
+ dodo tunnel configure --tunnel --public-url https://mcp.example.com --token-env DODO_OWNER_TUNNEL_TOKEN
73
+ # หรือไฟล์ private regular file ที่เป็น absolute path
74
+ dodo tunnel configure --tunnel --public-url https://mcp.example.com --token-file /private/path/tunnel-token
42
75
  ```
43
76
 
44
- หน้า Local Config มีช่อง **Temporary Tunnel token** สำหรับเริ่ม tunnel ใน process ที่
45
- เปิดอยู่ได้ทันที และมีปุ่มหยุดเฉพาะ child ที่ process นี้เป็นเจ้าของ ช่องถูกล้างหลังส่ง
46
- Backend ตอบ `tokenStored:false` และไม่เขียนค่าลง config หรือ credential store
77
+ ค่า environment/file เป็น reference ที่เจ้าของดูแลเอง คำสั่งตรวจค่าและไฟล์ก่อนบันทึก
78
+ แต่ไม่คัดลอก token เข้า config เมื่อใช้หน้า Local Config token จะเป็น write-only และ
79
+ ถูกส่งตรงไปยัง OS credential store; หลังบันทึก UI แสดงเพียงว่ามี credential
47
80
 
48
- ## เปิด Remote Config ผ่าน Tunnel ชั่วคราว
81
+ ทุก `dodo start` ใน Tunnel mode จะอ่าน credential จาก reference เริ่ม `cloudflared`
82
+ ผ่าน child environment และรอ readiness ก่อนถือว่า startup สำเร็จ Token ไม่อยู่ใน
83
+ CLI argument, config JSON, log, audit, MCP response, browser storage หรือ environment
84
+ ของ MCP jobs เมื่อ DODO หยุด child ที่ process นี้เป็นเจ้าของจะหยุดตาม
85
+
86
+ ## เปลี่ยนโหมดจากหน้าเว็บ
87
+
88
+ เปิด Local Config จาก URL ที่ DODO แสดงบนเครื่องเจ้าของ แล้วใช้ส่วน
89
+ **การเชื่อมต่อ MCP**:
90
+
91
+ 1. ตั้ง Public origin เป็น HTTPS origin ของ Tunnel
92
+ 2. เลือก `DODO Tunnel`
93
+ 3. ใส่ Cloudflare Tunnel token หากยังไม่มี credential ที่บันทึกไว้
94
+ 4. กดบันทึกและ restart DODO
95
+
96
+ หรือเลือก `Local` แล้วบันทึกและ restart ค่าในหน้าเว็บไม่เปลี่ยน endpoint ของ process
97
+ ที่กำลังรันอยู่ทันที หน้าเว็บจะแสดงสถานะ restart ที่ตรงกับ runtime จริง
98
+
99
+ ## Remote Config ไม่เกินหนึ่งชั่วโมง
100
+
101
+ Remote Config ใช้ได้เฉพาะเมื่อเลือก Tunnel และ DODO-owned Tunnel กำลังรันอยู่:
49
102
 
50
103
  ```bash
51
104
  dodo --web
@@ -56,27 +109,17 @@ dodo web --status
56
109
  dodo web --close
57
110
  ```
58
111
 
59
- `dodo --web` ทำงานได้สองกรณี: ถ้ายังไม่มี DODO process จะเริ่ม MCP ที่พอร์ต 21730,
60
- Local Config ที่ 21731 และ Tunnel แล้วเปิด Remote Config; ถ้า process กำลังรันอยู่จะ
61
- ใช้ authenticated private IPC เพื่อเปิดหรือต่ออายุ lease โดยไม่ restart MCP หาก
62
- process เดิมเริ่มแบบ local-only คำสั่งจะถาม run-scoped Tunnel token แบบซ่อนและส่งให้
63
- process ที่รันอยู่ครั้งเดียว Token ไม่ถูกบันทึกและไม่อยู่ใน argv หรือ audit
64
-
65
- URL ไม่มี query/fragment secret ผู้ใช้ต้องกรอก pairing code ที่ terminal แสดง Code
66
- มีอายุไม่เกิน 10 นาทีและใช้ได้ครั้งเดียว จากนั้น server ออก session cookie ที่เป็น
67
- `Secure`, `HttpOnly`, `SameSite=Strict` และ `Path=/config` ตัว lease ปิดอัตโนมัติ
68
- ภายใน 1 ชั่วโมง เมื่อหมดอายุ `/config`, assets และ owner API ใต้ namespace นี้กลับ
69
- เป็น 404 การรัน `dodo --web` อีกครั้งยกเลิก code/session เดิมและออกชุดใหม่
70
-
71
- Remote Config เป็น authenticated bridge ไปยัง Local Config เดิม การเขียนทุกครั้งยัง
72
- ตรวจ workspace ID/epoch, owner validation และ policy ของ Local Config ไม่มี MCP tool
73
- สำหรับเปิด lease และ `dodo web --close` ปิดเฉพาะหน้าเว็บ โดยไม่หยุด MCP หรือ Tunnel
112
+ `/config` ตอบ 404 ตามค่าเริ่มต้น คำสั่ง `dodo --web` เปิดหรือเปลี่ยน lease ผ่าน
113
+ authenticated private IPC โดยไม่ restart MCP และไม่รับ Tunnel token ผ่าน IPC URL ไม่มี
114
+ query/fragment secret Pairing code มีอายุสั้นและใช้ได้ครั้งเดียว หลังจับคู่ browser ได้
115
+ cookie แบบ `Secure`, `HttpOnly`, `SameSite=Strict`, `Path=/config` Lease ปิดเองภายใน
116
+ หนึ่งชั่วโมง การเปิดใหม่ยกเลิก code/session เดิม
74
117
 
75
- ถ้ามี external tunnel ที่เจ้าของรันอยู่แล้วและไม่ต้องการให้ DODO เริ่ม cloudflared ใช้
76
- `dodo start --web --no-tunnel` จาก interactive terminal การเลือกนี้บอกเพียงว่า route
77
- ภายนอกมีอยู่แล้ว; DODO ไม่กล่าวอ้างหรือสร้าง route/DNS ให้เอง
118
+ Remote Config proxy ไปยัง Local Config loopback โดยคง Host/Origin/proxy-header checks,
119
+ private capability, rate limit, workspace ID/epoch และ owner policy เดิม การปิด lease
120
+ ไม่หยุด MCP หรือ Tunnel และไม่มี MCP tool สำหรับเปิดหน้า owner นี้
78
121
 
79
- คำสั่งตรวจสถานะที่ไม่มี secret:
122
+ ## ตรวจสถานะและแก้ปัญหา
80
123
 
81
124
  ```bash
82
125
  dodo tunnel status
@@ -85,41 +128,20 @@ dodo tunnel logs --lines 100
85
128
  dodo tunnel stop
86
129
  ```
87
130
 
88
- `status` ใช้ authenticated owner IPC และไม่อ่าน credential ส่วน `doctor` เป็นคำสั่งตรวจแบบ explicit: ตรวจ executable, credential availability, local `/healthz`, managed `/ready` และ public `/healthz` แยกกัน Public health ที่ผ่านไม่ได้แปลว่า AI client เชื่อมต่ออยู่
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 เก่า
89
135
 
90
- ## ขอบเขตของ token
136
+ ## ตั้งค่า MCP client
91
137
 
92
- DODO ไม่รับ token เป็น CLI argument และไม่ใส่ token ใน `cloudflared` argv เส้นทาง
93
- มาตรฐานไม่ใช้ macOS Keychain, Windows Credential Manager, Linux Secret Service,
94
- token file หรือ shell environment Token จาก terminal และ Local Config ถูกส่งผ่าน
95
- environment ของ child ที่สร้างเองเท่านั้น Tunnel diagnostics ถูกจำกัดขนาดและ redact
96
- ก่อนเขียนลง private state MCP jobs จะไม่ได้รับ `TUNNEL_TOKEN` หรือ
97
- `TUNNEL_TOKEN_FILE` แม้ owner จะใส่ชื่อไว้ใน environment allowlist
98
-
99
- คำสั่ง `dodo tunnel configure/start` และ credential reference รุ่นเดิมยังคงอยู่เพื่อ
100
- advanced/headless compatibility แต่ไม่ถูกเรียกโดย `dodo start`, `dodo --cli` หรือ
101
- Local Config และต้องเกิดจากคำสั่ง owner โดยตรง
102
-
103
- runtime ใช้ bounded restart หลัง `cloudflared` จบ retry ของตัวเอง และ `stop` ส่งสัญญาณเฉพาะ live child handle ที่ supervisor เป็นผู้สร้าง ไม่มีการ kill saved PID หรือ process ชื่อเหมือนกัน
104
-
105
- ## Client setup
106
-
107
- 1. ใช้ `https://mcp.example.com/mcp` ใน MCP client
138
+ 1. ใช้ `https://mcp.example.com/mcp`
108
139
  2. เลือก OAuth
109
- 3. คัดลอก exact callback จาก client
110
- 4. ลงทะเบียนด้วย `dodo auth add-client --redirect-uri ...`
111
- 5. ทำ browser authorization และ approve interaction จาก terminal
140
+ 3. คัดลอก exact callback URL จาก client
141
+ 4. ลงทะเบียนด้วย `dodo auth add-client --redirect-uri <exact-callback>`
142
+ 5. ทำ browser authorization และ approve interaction จาก owner terminal
112
143
  6. scan tools และตรวจ surface ที่ client รายงาน
113
144
 
114
- ## หลายโปรเจกต์
115
-
116
- หนึ่ง DODO process มี default workspace หนึ่งตัว แต่ Installation Runtime Manager เปิด
117
- explicit target runtimes หลายโปรเจกต์พร้อมกันได้ภายใต้ project registry และ target
118
- authority เดียวกัน จึงไม่ต้องสร้าง Tunnel แยกต่อโปรเจกต์ งานแต่ละ target ใช้
119
- workspace identity/epoch, jobs และ mutation queue ของตัวเอง
120
-
121
- ## Security
122
-
123
- ห้ามปิด OAuth, ใช้ Tunnel token แทน MCP OAuth, ส่ง token/pairing/session ใน URL หรือ
124
- เปิด CORS กว้าง Tunnel provider ไม่ได้แทน owner consent ของ DODO และ DODO ไม่เรียก
125
- Cloudflare API, ไม่จัดการ DNS, ไม่สร้าง/ลบ Tunnel และไม่เปิด firewall
145
+ หนึ่ง Tunnel รองรับ project registry ทั้ง installation ไม่ต้องสร้าง Tunnel ต่อโปรเจกต์
146
+ แต่ทุก target ยังตรวจ OAuth identity, target ACL/scopes, workspace identity/epoch,
147
+ trust/approval, path/secret guards, sandbox และ expected hash ตามเดิม
package/docs/WINDOWS.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # DODO Windows Candidate — Deferred
2
2
 
3
- DODO MCP 1.0.2 ยังไม่ประกาศ native Windows support โค้ดและ tests สำหรับ Windows
3
+ DODO MCP 1.0.3 ยังไม่ประกาศ native Windows support โค้ดและ tests สำหรับ Windows
4
4
  บางส่วนมีอยู่เป็น candidate สำหรับ phase สุดท้าย แต่ยังไม่มีหลักฐานจาก Windows 11 จริง
5
5
  และ Linux Docker ไม่สามารถใช้แทนหลักฐาน Windows ได้
6
6
 
@@ -1,10 +1,16 @@
1
1
  # ADR-031 — Owner-selected Cloudflare Tunnel supervision
2
2
 
3
- **Status:** accepted (implemented)
3
+ **Status:** superseded in credential/lifecycle selection by ADR-048. Process ownership,
4
+ readiness, redaction and bounded-stop decisions remain accepted.
4
5
 
5
6
  **Decision.** DODO starts only an owner-selected, trusted `cloudflared` executable owned by the current HTTP process. `dodo start` can attach it immediately; the authenticated Local Config can start or stop the same process-owned runtime. The owner still creates the remotely-managed Tunnel, public hostname and DNS in Cloudflare.
6
7
 
7
- The default flow uses a run-scoped token entered through a hidden terminal prompt or the authenticated loopback Local Config. The token is never persisted in global config or an OS credential store. A process-owned `TunnelRuntime` forwards it through the managed child's dedicated environment and stops that child with DODO; it is absent from argv, logs, MCP, audit and job environments. Explicit secure credential references remain supported only by advanced standalone tunnel commands for compatibility.
8
+ The original flow used a run-scoped token entered through a hidden terminal prompt or
9
+ the authenticated loopback Local Config. ADR-048 replaces that credential flow with one
10
+ persistent Local/Tunnel selection and a reviewed credential reference. The invariant
11
+ retained from this ADR is that a process-owned `TunnelRuntime` forwards the resolved
12
+ credential through the child environment, stops that child with DODO and keeps the
13
+ credential out of argv, logs, MCP, audit and job environments.
8
14
 
9
15
  The supervisor uses a separate authenticated singleton owner IPC endpoint, loopback readiness, bounded restarts, private bounded/redacted logs and live child handles for termination. It never signals a saved PID or searches by process name. Local Config and the metrics listener remain loopback-only and are not part of the public route.
10
16
 
@@ -29,9 +29,9 @@ in-flight drain, running-job refusal, resource teardown และ fresh workspac
29
29
  data plane หากล้มเหลว launcher ยังคงปิด data plane ไม่มี half-switched state
30
30
 
31
31
  `dodo --cli` เป็น terminal menu ที่เรียก owner operations เดิม ไม่ใช่ MCP tool
32
- เมนูไม่เปลี่ยน OAuth grant, access mode หรือ OS permission เมนูเริ่มปกติเรียก hidden
33
- run-scoped Tunnel prompt เดียวกับ `dodo start` ส่วนเมนู local-only ส่ง override สำหรับ
34
- รอบนั้น Token ไม่ผ่าน readline menu, argv, config หรือ OS credential provider
32
+ เมนูไม่เปลี่ยน OAuth grant, access mode หรือ OS permission เมนูเริ่ม DODO ตาม
33
+ persistent `connectionMode` ที่เจ้าของบันทึกไว้ การเลือก/เก็บ Tunnel credential ใช้
34
+ Local Config หรือ `dodo tunnel configure` ตาม ADR-048 และไม่ผ่าน readline menu/argv
35
35
 
36
36
  STDIO คง contract `dodo stdio --root PATH` เพราะ client เป็นเจ้าของ subprocess
37
37
  lifecycle และต้องกำหนด root อย่างชัดเจน
@@ -25,10 +25,10 @@ and leaves Local Config's workspace/epoch checks, validation, policy and audit a
25
25
  authority. Pairing never grants MCP scopes, project access, trust or approvals. No MCP
26
26
  tool can open the lease.
27
27
 
28
- When an already-running local-only DODO process receives `dodo --web`, the CLI reads a
29
- temporary Tunnel token without echo, sends it once over authenticated owner IPC, and
30
- the owning process starts `cloudflared`. The token is not persisted or placed in argv.
31
- Closing or expiry affects only Remote Config; MCP, OAuth and Tunnel continue running.
28
+ ADR-048 supersedes the temporary-token handoff: current source opens Remote Config only
29
+ when the saved connection mode is Tunnel and the DODO-owned Tunnel is already running.
30
+ Owner IPC cannot carry a Tunnel credential or switch the connection mode. Closing or
31
+ expiry still affects only Remote Config; MCP, OAuth and Tunnel continue running.
32
32
 
33
33
  ## Consequences
34
34
 
@@ -0,0 +1,58 @@
1
+ # ADR-048 — Persistent exclusive Local/Tunnel connection mode
2
+
3
+ ## Status
4
+
5
+ Accepted in source; unreleased.
6
+
7
+ ## Context
8
+
9
+ The prior launch flow asked for a run-scoped Cloudflare Tunnel token each time and could
10
+ start locally when the owner omitted it. That made the advertised endpoint depend on an
11
+ interactive answer and allowed a configuration mistake to change the connection path.
12
+ Owners need one installation-wide choice: always use the DODO Tunnel or always use the
13
+ local endpoint until they explicitly change it.
14
+
15
+ ## Decision
16
+
17
+ Global owner config stores one authoritative `connectionMode`: `local` or `tunnel`.
18
+ Legacy `mode` and `startWithDodo` fields are accepted only during load-time migration
19
+ and are removed from newly saved config.
20
+
21
+ In Local mode DODO advertises `http://127.0.0.1:<port>/mcp`, uses the loopback origin as
22
+ its OAuth issuer/resource and never starts `cloudflared`. In Tunnel mode DODO advertises
23
+ the configured public HTTPS origin, starts one process-owned supervisor on every
24
+ `dodo start`, waits for readiness and stops that child with DODO. Missing credentials,
25
+ executable failure or readiness failure aborts startup; there is no Local fallback.
26
+
27
+ The standard credential path is a reviewed OS store: macOS Keychain, Windows Credential
28
+ Manager or Linux Secret Service. Global config stores only an opaque reference. Explicit
29
+ environment/private-file references remain for owner-controlled headless deployments.
30
+ The token reaches `cloudflared` through its dedicated child environment and never enters
31
+ argv, config, logs, audit, browser storage, MCP output or MCP-job environments.
32
+
33
+ Local Config may select the mode and accept a write-only token after its existing owner,
34
+ Host, Origin, proxy-header, rate and workspace-context checks. The backend writes it to
35
+ the OS store and returns only presence/provider. A saved change requires restart and the
36
+ UI distinguishes saved mode from the active runtime endpoint.
37
+
38
+ Remote Config remains a bounded one-hour `/config` lease. It is available only while
39
+ Tunnel mode and the DODO-owned supervisor are active. Installation IPC cannot transmit a
40
+ Tunnel token or change mode, and public MCP routes expose no owner control operation.
41
+
42
+ ## Consequences
43
+
44
+ - Startup and endpoint selection are deterministic and auditable.
45
+ - Tunnel mode fails closed instead of silently changing how clients reach DODO.
46
+ - Credential unlock may still require an owner/OS interaction at startup.
47
+ - The loopback MCP listener remains present as the private Tunnel upstream, but the
48
+ public URL is the only advertised active MCP endpoint in Tunnel mode.
49
+ - Switching modes or public origin requires a DODO restart and client reconnect/rescan.
50
+
51
+ ## Evidence
52
+
53
+ - `tests/integration/connectionMode.test.ts`
54
+ - `tests/security/tunnelCredentials.test.ts`
55
+ - `tests/security/localConfig.test.ts`
56
+ - `tests/integration/tunnelSupervisor.test.ts`
57
+ - `tests/security/remoteConfig.test.ts`
58
+ - `tests/integration/remoteConfigUi.test.ts`
@@ -63,3 +63,4 @@ decisions; ADR-011+ resolve implementation ambiguities without reducing security
63
63
  - [045 — Explicit project runtimes and provider-backed sub-agents](045-ai-providers-multiproject.md)
64
64
  - [046 — Personal add-and-use mode](046-personal-access-mode.md)
65
65
  - [047 — Temporary Remote Config on the tunneled listener](047-temporary-remote-config.md)
66
+ - [048 — Persistent exclusive Local/Tunnel connection mode](048-persistent-connection-mode.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dodo-mcp",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
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",
@@ -41,6 +41,7 @@
41
41
  "docs/RELEASE_1.0.0.md",
42
42
  "docs/RELEASE_1.0.1.md",
43
43
  "docs/RELEASE_1.0.2.md",
44
+ "docs/RELEASE_1.0.3.md",
44
45
  "docs/RELEASE_NOTES.md",
45
46
  "docs/SCHEDULES.md",
46
47
  "docs/SECURITY.md",
@@ -359,25 +359,20 @@
359
359
  },
360
360
  "tunnel": {
361
361
  "default": {
362
- "mode": "external",
363
- "startWithDodo": true,
362
+ "connectionMode": "local",
364
363
  "metricsPort": 21732,
365
364
  "maxRestarts": 2
366
365
  },
367
366
  "type": "object",
368
367
  "properties": {
369
- "mode": {
370
- "default": "external",
368
+ "connectionMode": {
369
+ "default": "local",
371
370
  "type": "string",
372
371
  "enum": [
373
- "external",
374
- "managed"
372
+ "local",
373
+ "tunnel"
375
374
  ]
376
375
  },
377
- "startWithDodo": {
378
- "default": true,
379
- "type": "boolean"
380
- },
381
376
  "credentialRef": {
382
377
  "oneOf": [
383
378
  {
@@ -456,8 +451,7 @@
456
451
  }
457
452
  },
458
453
  "required": [
459
- "mode",
460
- "startWithDodo",
454
+ "connectionMode",
461
455
  "metricsPort",
462
456
  "maxRestarts"
463
457
  ],