dodo-mcp 1.0.2 → 1.0.4
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.
- package/README.md +37 -17
- package/dist/cli/main.js +68 -68
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/menu.js +25 -20
- package/dist/cli/menu.js.map +1 -1
- package/dist/config/globalConfig.js +23 -1
- package/dist/config/globalConfig.js.map +1 -1
- package/dist/config/stateImport.js +2 -2
- package/dist/config/stateImport.js.map +1 -1
- package/dist/config/tunnelConfig.js +2 -3
- package/dist/config/tunnelConfig.js.map +1 -1
- package/dist/platform/privateFs.js +3 -1
- package/dist/platform/privateFs.js.map +1 -1
- package/dist/server/appServer.js +38 -26
- package/dist/server/appServer.js.map +1 -1
- package/dist/server/configUi/app.js +35 -58
- package/dist/server/configUi/index.html +21 -22
- package/dist/server/localConfig.js +50 -63
- package/dist/server/localConfig.js.map +1 -1
- package/dist/tunnel/control.js +4 -6
- package/dist/tunnel/control.js.map +1 -1
- package/dist/tunnel/credentials.js +5 -19
- package/dist/tunnel/credentials.js.map +1 -1
- package/dist/tunnel/runtime.js +3 -4
- package/dist/tunnel/runtime.js.map +1 -1
- package/dist/tunnel/supervisor.js +8 -10
- package/dist/tunnel/supervisor.js.map +1 -1
- package/docs/AI_PROVIDERS.md +1 -1
- package/docs/ARCHITECTURE.md +14 -14
- package/docs/COMPATIBILITY.md +10 -2
- package/docs/MANUAL_ACCEPTANCE.md +40 -16
- package/docs/RELEASE_1.0.0.md +3 -0
- package/docs/RELEASE_1.0.3.md +56 -0
- package/docs/RELEASE_1.0.4.md +34 -0
- package/docs/RELEASE_NOTES.md +32 -0
- package/docs/SECURITY.md +26 -10
- package/docs/TEST_REPORT.md +51 -1
- package/docs/TUNNEL.md +102 -80
- package/docs/WINDOWS.md +4 -1
- package/docs/WINDOWS_SETUP.md +182 -0
- package/docs/adr/031-owner-selected-tunnel-supervision.md +8 -2
- package/docs/adr/044-global-launcher-and-cli-menu.md +3 -3
- package/docs/adr/047-temporary-remote-config.md +4 -4
- package/docs/adr/048-persistent-connection-mode.md +58 -0
- package/docs/adr/README.md +1 -0
- package/package.json +4 -1
- package/schemas/global-config.schema.json +6 -12
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DODO MCP
|
|
2
2
|
|
|
3
|
-
**DODO MCP 1.0.
|
|
3
|
+
**DODO MCP 1.0.4** คือ MCP server แบบ local-first สำหรับให้ AI ช่วยพัฒนา software โดยทำงานกับ workspace ที่เจ้าของเลือก ค่าเริ่มต้นเป็นโหมดส่วนตัวแบบเพิ่มโปรเจกต์แล้วใช้ได้ทันที และยังมีโหมด managed สำหรับแยก workspace ACL/trust แบบละเอียด
|
|
4
4
|
|
|
5
5
|
## จุดเด่น
|
|
6
6
|
|
|
@@ -57,6 +57,12 @@ expected hash, workspace context และ OAuth scopes หากต้องแ
|
|
|
57
57
|
|
|
58
58
|
`dodo setup --check` และ `dodo setup --plan` เป็น read-only หากแผนมี dependency ที่ต้องติดตั้ง ให้ตรวจรายการก่อนแล้วจึงรัน `dodo setup --yes --components <list>` ระบบจะไม่เริ่ม installer หากไม่มี `--yes` และ `--yes` ไม่ข้าม sudo, OS permission หรือ owner consent
|
|
59
59
|
|
|
60
|
+
บน Windows ให้รัน DODO ด้วย account เดียวกับที่จะใช้งานประจำและเก็บ state บน local
|
|
61
|
+
NTFS หากพบ `private Windows state ACL could not be established or verified` ห้ามลด
|
|
62
|
+
ACL หรือลบ state เดิม ดูวิธีใช้ `DODO_CONFIG_DIR` เพื่อเริ่ม installation ใหม่อย่าง
|
|
63
|
+
ปลอดภัยและรายการ component ที่ต้องติดตั้งเองใน [Windows Setup](docs/WINDOWS_SETUP.md)
|
|
64
|
+
โดยเฉพาะ `cloudflared` ซึ่ง Windows ต้องติดตั้ง signed package จาก Cloudflare ก่อน
|
|
65
|
+
|
|
60
66
|
เมื่อ DODO ตรวจพบ config เดิมในตำแหน่งมาตรฐาน สามารถใช้ `dodo setup --import-state` เพื่อนำเข้าเฉพาะ preference ที่ปลอดภัย เช่น port, search backend, retention และ tool surface ระบบจะสร้าง installation identity ใหม่เสมอและไม่คัดลอก OAuth keys/tokens, client grants, workspace ACL, trust, approvals, schedules, public origin, web/desktop permission, executable registration หรือฐานข้อมูลเดิม ต้นฉบับจะไม่ถูกแก้ไข
|
|
61
67
|
|
|
62
68
|
เปิด Local Config จาก URL ที่ `dodo` แสดงใน terminal ใช้สำหรับเพิ่มโปรเจกต์, AI
|
|
@@ -250,20 +256,34 @@ command sandbox, idempotency และ audit เดิม `agent_exec` ใช้
|
|
|
250
256
|
|
|
251
257
|
### เชื่อม Remote MCP ผ่าน Cloudflare Tunnel
|
|
252
258
|
|
|
253
|
-
DODO
|
|
259
|
+
DODO เลือกวิธีเชื่อมต่อระดับ installation ได้หนึ่งแบบ: `local` หรือ `tunnel` ค่าที่เลือก
|
|
260
|
+
มีผลกับทุก `dodo start` และไม่มี fallback อัตโนมัติ หากเลือก Tunnel แล้ว credential หรือ
|
|
261
|
+
readiness ไม่พร้อม DODO จะหยุด startup แทนการเปิด local โดยไม่แจ้ง
|
|
262
|
+
|
|
263
|
+
Local mode:
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
dodo tunnel configure --local
|
|
267
|
+
dodo start
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
DODO Tunnel mode:
|
|
254
271
|
|
|
255
272
|
```bash
|
|
256
273
|
dodo setup --check --components cloudflared
|
|
257
|
-
dodo
|
|
274
|
+
dodo tunnel configure \
|
|
275
|
+
--tunnel \
|
|
276
|
+
--public-url https://mcp.example.com \
|
|
277
|
+
--os-credential
|
|
258
278
|
dodo start
|
|
259
|
-
# Cloudflare Tunnel token (temporary; Enter = local only):
|
|
260
279
|
```
|
|
261
280
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
281
|
+
เจ้าของสร้าง remotely-managed Tunnel, public hostname และ DNS ใน Cloudflare และ route
|
|
282
|
+
**ทุก path** มาที่ `http://127.0.0.1:21730` DODO ไม่จัดการ Cloudflare account/DNS
|
|
283
|
+
Token ถูกบันทึกใน macOS Keychain, Windows Credential Manager หรือ Linux Secret Service;
|
|
284
|
+
global config เก็บเพียง opaque reference ทุกครั้งที่เปิด DODO ระบบจะเริ่ม
|
|
285
|
+
`cloudflared` เป็น child และหยุด child พร้อม DODO Token ไม่อยู่ใน argv, config, log,
|
|
286
|
+
audit, browser storage, MCP response หรือ environment ของ MCP jobs
|
|
267
287
|
|
|
268
288
|
หากต้องตั้งค่าจากอุปกรณ์อื่น ให้เปิดหน้าเจ้าของแบบชั่วคราวผ่าน public listener เดียวกัน:
|
|
269
289
|
|
|
@@ -277,15 +297,14 @@ dodo web --close # ปิดหน้า Remote Config แต่ MCP/Tunnel
|
|
|
277
297
|
terminal หลังจับคู่ browser จะใช้ cookie ที่เป็น `Secure`, `HttpOnly`,
|
|
278
298
|
`SameSite=Strict` และจำกัด path ที่ `/config` หน้า Remote Config ปิดอัตโนมัติภายใน
|
|
279
299
|
1 ชั่วโมง การเปิดใหม่ออก code/session ใหม่และไม่ต้อง restart MCP ไม่มี token หรือ
|
|
280
|
-
credential อยู่ใน URL
|
|
281
|
-
token
|
|
300
|
+
credential อยู่ใน URL คำสั่งนี้ใช้ได้เฉพาะเมื่อ persistent mode เป็น `tunnel` และ
|
|
301
|
+
DODO-owned Tunnel กำลังทำงาน; มันไม่รับ token ใหม่ผ่าน IPC และไม่เปลี่ยนจาก Local เอง
|
|
282
302
|
|
|
283
|
-
หน้า Local Config ที่ `127.0.0.1:21731`
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
`dodo tunnel
|
|
287
|
-
`dodo tunnel
|
|
288
|
-
โดยเจ้าของอย่างชัดเจน; เส้นทาง `dodo start` และหน้าเว็บไม่ใช้ credential store
|
|
303
|
+
หน้า Local Config ที่ `127.0.0.1:21731` เลือก Local/Tunnel, ตั้ง public origin และบันทึก
|
|
304
|
+
token แบบ write-only เข้า OS credential store ได้ ค่าใหม่มีผลหลัง restart หน้าเว็บแสดง
|
|
305
|
+
Active MCP URL จาก runtime จริงและไม่กล่าวว่า Tunnel หรือ AI client เชื่อมแล้วหากไม่มี
|
|
306
|
+
หลักฐาน ดูสถานะด้วย `dodo tunnel status`, ตรวจ connectivity ด้วย `dodo tunnel doctor`
|
|
307
|
+
และดู log ที่ redacted ด้วย `dodo tunnel logs`
|
|
289
308
|
|
|
290
309
|
บน macOS `dodo setup --yes` เลือก component `cloudflared` อยู่ในชุด `all` แล้ว
|
|
291
310
|
Tunnel route ต้องชี้เฉพาะ MCP/OAuth listener `21730` ห้ามชี้ Local Config `21731`
|
|
@@ -394,6 +413,7 @@ dodo desktop allow --app com.google.Chrome --mode control --persist --yes
|
|
|
394
413
|
- [Tunnel](docs/TUNNEL.md)
|
|
395
414
|
- [Web clients](docs/WEB_CLIENTS.md)
|
|
396
415
|
- [Windows compatibility](docs/WINDOWS.md)
|
|
416
|
+
- [Windows setup และ ACL troubleshooting](docs/WINDOWS_SETUP.md)
|
|
397
417
|
- [Task assistance](docs/ASSISTANCE.md)
|
|
398
418
|
- [Multimodal](docs/MULTIMODAL.md)
|
|
399
419
|
- [Project Registry](docs/PROJECTS.md)
|
package/dist/cli/main.js
CHANGED
|
@@ -142,21 +142,23 @@ async function launchHttp(opts) {
|
|
|
142
142
|
const configDir = resolveConfigDir(process.env).dir;
|
|
143
143
|
const configFile = statePaths(configDir).configFile;
|
|
144
144
|
const config = loadGlobalConfig(configFile);
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (opts.web
|
|
157
|
-
fail('Remote Config
|
|
158
|
-
|
|
145
|
+
const connectionMode = config.tunnel.connectionMode;
|
|
146
|
+
const publicUrl = config.publicUrl;
|
|
147
|
+
const selectedPort = opts.port ?? config.port;
|
|
148
|
+
if (connectionMode === 'tunnel') {
|
|
149
|
+
if (!publicUrl)
|
|
150
|
+
fail('Tunnel mode requires a public HTTPS origin; run dodo tunnel configure --tunnel --os-credential --public-url https://your-host');
|
|
151
|
+
validatePublicUrl(publicUrl, false);
|
|
152
|
+
if (!config.tunnel.credentialRef)
|
|
153
|
+
fail('Tunnel mode requires a saved credential; run dodo tunnel configure --tunnel --os-credential --public-url https://your-host');
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
if (opts.web)
|
|
157
|
+
fail('Remote Config requires Tunnel mode; configure Tunnel, restart DODO, then run dodo --web');
|
|
158
|
+
if (selectedPort === 0)
|
|
159
|
+
fail('local connection mode requires a concrete MCP port');
|
|
159
160
|
}
|
|
161
|
+
const endpointOrigin = connectionMode === 'tunnel' ? publicUrl : `http://127.0.0.1:${selectedPort}`;
|
|
160
162
|
const startOpts = {
|
|
161
163
|
configPort: config.configPort,
|
|
162
164
|
...(opts.bypass ? { runMode: 'bypass' } : opts.allow ? { runMode: 'allow-all' } : {}),
|
|
@@ -165,12 +167,11 @@ async function launchHttp(opts) {
|
|
|
165
167
|
allowUnsafeRoot: opts.allowUnsafeRoot ?? false,
|
|
166
168
|
quiet: opts.quiet ?? false,
|
|
167
169
|
onLog: (line) => console.log(formatTerminalLine(line)),
|
|
168
|
-
|
|
170
|
+
connectionMode,
|
|
171
|
+
publicUrlOverride: endpointOrigin,
|
|
169
172
|
};
|
|
170
173
|
if (opts.port !== undefined && !Number.isNaN(opts.port))
|
|
171
174
|
startOpts.portOverride = opts.port;
|
|
172
|
-
if (opts.publicUrl !== undefined)
|
|
173
|
-
startOpts.publicUrlOverride = opts.publicUrl;
|
|
174
175
|
if (opts.tools !== undefined)
|
|
175
176
|
startOpts.toolSurface = opts.tools;
|
|
176
177
|
const tunnelRuntime = new TunnelRuntime(configDir, line => console.log(`[dodo:tunnel] ${line}`));
|
|
@@ -184,32 +185,26 @@ async function launchHttp(opts) {
|
|
|
184
185
|
void stop('server stop');
|
|
185
186
|
},
|
|
186
187
|
});
|
|
187
|
-
if (
|
|
188
|
+
if (connectionMode === 'tunnel') {
|
|
188
189
|
try {
|
|
189
|
-
const
|
|
190
|
+
const selectedConfig = GlobalConfigSchema.parse({
|
|
190
191
|
...config,
|
|
191
192
|
...(publicUrl ? { publicUrl } : {}),
|
|
192
|
-
tunnel: { ...config.tunnel,
|
|
193
|
+
tunnel: { ...config.tunnel, connectionMode: 'tunnel' },
|
|
193
194
|
});
|
|
194
|
-
const status = await tunnelRuntime.start(
|
|
195
|
-
console.log(`[dodo]
|
|
195
|
+
const status = await tunnelRuntime.start(selectedConfig);
|
|
196
|
+
console.log(`[dodo] DODO Tunnel started: ${status.publicOrigin}/mcp`);
|
|
197
|
+
if (opts.web)
|
|
198
|
+
printRemoteConfig(await ipcForInstallation('remoteConfig.open'));
|
|
196
199
|
}
|
|
197
200
|
catch (error) {
|
|
198
201
|
suppressOnStopped = true;
|
|
199
202
|
await server.close();
|
|
200
203
|
throw error;
|
|
201
204
|
}
|
|
202
|
-
finally {
|
|
203
|
-
temporaryTunnelToken = undefined;
|
|
204
|
-
}
|
|
205
205
|
}
|
|
206
|
-
else if (!opts.quiet
|
|
207
|
-
|
|
208
|
-
console.log('[dodo] Tunnel skipped: configure the public HTTPS origin, then restart DODO.');
|
|
209
|
-
else if (!interactiveTerminal)
|
|
210
|
-
console.log('[dodo] Tunnel skipped: temporary token entry requires an interactive terminal.');
|
|
211
|
-
else
|
|
212
|
-
console.log('[dodo] Tunnel skipped for this run; local MCP remains available.');
|
|
206
|
+
else if (!opts.quiet) {
|
|
207
|
+
console.log(`[dodo] Local connection selected: ${endpointOrigin}/mcp`);
|
|
213
208
|
}
|
|
214
209
|
async function stop(signal) {
|
|
215
210
|
if (shutdownPromise)
|
|
@@ -278,9 +273,12 @@ program.command('setup')
|
|
|
278
273
|
}
|
|
279
274
|
catch (error) {
|
|
280
275
|
if (opts.json)
|
|
281
|
-
console.log(JSON.stringify({ error: error instanceof DodoError ? error.code : 'SETUP_FAILED', message: error.message }));
|
|
282
|
-
else
|
|
276
|
+
console.log(JSON.stringify({ error: error instanceof DodoError ? error.code : 'SETUP_FAILED', message: error.message, ...(error instanceof DodoError && error.recovery ? { recovery: error.recovery } : {}) }));
|
|
277
|
+
else {
|
|
283
278
|
console.error(`dodo setup: ${error.message}`);
|
|
279
|
+
if (error instanceof DodoError && error.recovery)
|
|
280
|
+
console.error(`Recovery: ${error.recovery}`);
|
|
281
|
+
}
|
|
284
282
|
process.exitCode = 1;
|
|
285
283
|
}
|
|
286
284
|
});
|
|
@@ -367,9 +365,12 @@ project.command('remove <projectId>')
|
|
|
367
365
|
// --------------------------------------------------------------- tunnel ----
|
|
368
366
|
const tunnel = program.command('tunnel').description('manage or inspect a Cloudflare remotely-managed Tunnel without managing DNS or the Cloudflare account');
|
|
369
367
|
tunnel.command('configure')
|
|
370
|
-
.description('
|
|
371
|
-
.option('--
|
|
372
|
-
.option('--
|
|
368
|
+
.description('select persistent local or DODO-owned Tunnel mode; tokens are never written to config')
|
|
369
|
+
.option('--tunnel', 'select the DODO-owned Tunnel for every dodo start', false)
|
|
370
|
+
.option('--local', 'select local-only MCP for every dodo start', false)
|
|
371
|
+
.option('--managed', 'deprecated alias for --tunnel', false)
|
|
372
|
+
.option('--external', 'deprecated alias for --local', false)
|
|
373
|
+
.option('--public-url <url>', 'public HTTPS origin used by the selected DODO Tunnel')
|
|
373
374
|
.option('--os-credential', 'prompt through the reviewed OS credential provider', false)
|
|
374
375
|
.option('--token-env <name>', 'reference an uppercase environment variable that already contains the token')
|
|
375
376
|
.option('--token-file <absolute-path>', 'reference an owner-private absolute token file')
|
|
@@ -381,8 +382,10 @@ tunnel.command('configure')
|
|
|
381
382
|
.option('--json', 'machine-readable non-secret result', false)
|
|
382
383
|
.action(async (opts) => {
|
|
383
384
|
try {
|
|
384
|
-
|
|
385
|
-
|
|
385
|
+
const chooseTunnel = opts.tunnel || opts.managed;
|
|
386
|
+
const chooseLocal = opts.local || opts.external;
|
|
387
|
+
if (chooseTunnel && chooseLocal)
|
|
388
|
+
fail('choose --tunnel or --local');
|
|
386
389
|
const methods = [opts.osCredential, opts.tokenEnv !== undefined, opts.tokenFile !== undefined].filter(Boolean).length;
|
|
387
390
|
if (methods > 1)
|
|
388
391
|
fail('choose only one of --os-credential, --token-env or --token-file');
|
|
@@ -393,6 +396,16 @@ tunnel.command('configure')
|
|
|
393
396
|
const { dir } = resolveConfigDir(process.env);
|
|
394
397
|
ensureConfigDir(dir);
|
|
395
398
|
const paths = statePaths(dir), current = loadGlobalConfig(paths.configFile);
|
|
399
|
+
const connectionMode = chooseTunnel ? 'tunnel' : chooseLocal ? 'local' : methods > 0 ? 'tunnel' : current.tunnel.connectionMode;
|
|
400
|
+
const publicUrl = opts.publicUrl !== undefined
|
|
401
|
+
? validatePublicUrl(opts.publicUrl, false).origin
|
|
402
|
+
: current.publicUrl;
|
|
403
|
+
if (connectionMode === 'tunnel' && !publicUrl)
|
|
404
|
+
fail('Tunnel mode requires --public-url https://your-host (or a previously saved public origin)');
|
|
405
|
+
if (connectionMode === 'tunnel' && publicUrl)
|
|
406
|
+
validatePublicUrl(publicUrl, false);
|
|
407
|
+
if (connectionMode === 'local' && opts.publicUrl !== undefined)
|
|
408
|
+
fail('--public-url is used only with --tunnel');
|
|
396
409
|
const credentials = await import('../tunnel/credentials.js');
|
|
397
410
|
const { resolveCloudflared } = await import('../tunnel/supervisor.js');
|
|
398
411
|
const previousCredentialRef = current.tunnel.credentialRef;
|
|
@@ -401,7 +414,7 @@ tunnel.command('configure')
|
|
|
401
414
|
credentialRef = undefined;
|
|
402
415
|
const baseTunnelInput = {
|
|
403
416
|
...current.tunnel,
|
|
404
|
-
|
|
417
|
+
connectionMode,
|
|
405
418
|
...(credentialRef ? { credentialRef } : {}),
|
|
406
419
|
...(opts.metricsPort !== undefined ? { metricsPort: opts.metricsPort } : {}),
|
|
407
420
|
...(opts.maxRestarts !== undefined ? { maxRestarts: opts.maxRestarts } : {}),
|
|
@@ -409,7 +422,7 @@ tunnel.command('configure')
|
|
|
409
422
|
};
|
|
410
423
|
if (!credentialRef)
|
|
411
424
|
delete baseTunnelInput['credentialRef'];
|
|
412
|
-
let next = GlobalConfigSchema.parse({ ...current, tunnel: baseTunnelInput });
|
|
425
|
+
let next = GlobalConfigSchema.parse({ ...current, ...(publicUrl ? { publicUrl } : {}), tunnel: baseTunnelInput });
|
|
413
426
|
if (opts.cloudflared !== undefined) {
|
|
414
427
|
const canonical = resolveCloudflared(next);
|
|
415
428
|
next = GlobalConfigSchema.parse({ ...next, tunnel: { ...next.tunnel, executable: canonical } });
|
|
@@ -427,19 +440,19 @@ tunnel.command('configure')
|
|
|
427
440
|
if (!credentialRef)
|
|
428
441
|
delete withCredential['credentialRef'];
|
|
429
442
|
next = GlobalConfigSchema.parse({ ...next, tunnel: withCredential });
|
|
430
|
-
if (next.tunnel.
|
|
431
|
-
fail('
|
|
443
|
+
if (next.tunnel.connectionMode === 'tunnel' && !next.tunnel.credentialRef)
|
|
444
|
+
fail('Tunnel mode requires --os-credential, --token-env or --token-file');
|
|
432
445
|
saveGlobalConfig(paths.configFile, next);
|
|
433
446
|
if (opts.removeCredential && previousCredentialRef)
|
|
434
447
|
credentials.deleteOsTunnelCredential(previousCredentialRef);
|
|
435
|
-
const result = {
|
|
448
|
+
const result = { connectionMode: next.tunnel.connectionMode, publicOrigin: next.tunnel.connectionMode === 'tunnel' ? next.publicUrl : null, credential: next.tunnel.credentialRef ? 'configured' : 'not-configured', metricsPort: next.tunnel.metricsPort, maxRestarts: next.tunnel.maxRestarts, cloudflared: next.tunnel.executable ? 'owner-selected' : 'trusted-PATH', started: false };
|
|
436
449
|
if (opts.json)
|
|
437
450
|
console.log(JSON.stringify(result, null, 2));
|
|
438
451
|
else {
|
|
439
|
-
console.log(`
|
|
452
|
+
console.log(`Connection mode: ${result.connectionMode}`);
|
|
440
453
|
console.log(`Credential: ${result.credential} (value is not stored in config or displayed)`);
|
|
441
454
|
console.log(`Readiness: 127.0.0.1:${result.metricsPort} Restarts: ${result.maxRestarts}`);
|
|
442
|
-
console.log(result.
|
|
455
|
+
console.log(result.connectionMode === 'tunnel' ? 'Saved. Every dodo start now owns this Tunnel and fails closed if it cannot start.' : 'Saved. Every dodo start now uses only the local MCP endpoint.');
|
|
443
456
|
}
|
|
444
457
|
}
|
|
445
458
|
catch (error) {
|
|
@@ -570,7 +583,7 @@ program.command('cli')
|
|
|
570
583
|
startupProject,
|
|
571
584
|
selectProject: selectStartupProject,
|
|
572
585
|
addProject: addAndSelectProject,
|
|
573
|
-
start: async (root
|
|
586
|
+
start: async (root) => launchHttp({ ...(root === undefined ? {} : { root }) }),
|
|
574
587
|
openRemoteConfig: openRemoteConfigFromCli,
|
|
575
588
|
setupAll: () => setupFromMenu(false),
|
|
576
589
|
checkSetup: () => setupFromMenu(true),
|
|
@@ -590,20 +603,7 @@ function printRemoteConfig(result) {
|
|
|
590
603
|
}
|
|
591
604
|
async function openRemoteConfigFromCli() {
|
|
592
605
|
try {
|
|
593
|
-
|
|
594
|
-
let tunnelToken;
|
|
595
|
-
if (status.tunnel?.available === true && status.tunnel.running !== true) {
|
|
596
|
-
const { readTemporaryTunnelToken } = await import('../tunnel/credentials.js');
|
|
597
|
-
tunnelToken = await readTemporaryTunnelToken('Cloudflare Tunnel token (temporary; required for Remote Config): ');
|
|
598
|
-
if (!tunnelToken)
|
|
599
|
-
fail('Remote Config needs a temporary Tunnel token; use dodo start --no-tunnel for local-only MCP');
|
|
600
|
-
}
|
|
601
|
-
try {
|
|
602
|
-
printRemoteConfig(await ipcForInstallation('remoteConfig.open', tunnelToken ? { tunnelToken } : {}));
|
|
603
|
-
}
|
|
604
|
-
finally {
|
|
605
|
-
tunnelToken = undefined;
|
|
606
|
-
}
|
|
606
|
+
printRemoteConfig(await ipcForInstallation('remoteConfig.open'));
|
|
607
607
|
}
|
|
608
608
|
catch (error) {
|
|
609
609
|
if (!(error instanceof IpcError) || !error.message.includes('no running DODO installation'))
|
|
@@ -641,13 +641,11 @@ program
|
|
|
641
641
|
.description('start the MCP server for the saved project, or wait for a project selection (foreground)')
|
|
642
642
|
.option('--root <path>', 'select and remember an explicit workspace root (local CLI only)')
|
|
643
643
|
.option('--port <n>', 'listen port (default from config, initially 21730)', (v) => Number.parseInt(v, 10))
|
|
644
|
-
.option('--public-url <url>', 'public origin override for this run (local CLI only)')
|
|
645
644
|
.option('--allow-unsafe-root', 'permit filesystem root / home / other unusually broad workspace roots', false)
|
|
646
645
|
.option('--allow', 'allow task execution for this run (requires --all)', false)
|
|
647
646
|
.option('--all', 'all local action classes (requires --allow)', false)
|
|
648
647
|
.option('--bypass', 'trusted actions and no default job sandbox for this run; OAuth and file guards remain', false)
|
|
649
648
|
.option('--tools <surface>', 'tool exposure for this run: compact (HTTP default), full, or hybrid (49 tools: gateways + common direct tools); permissions are unchanged')
|
|
650
|
-
.option('--no-tunnel', 'start local MCP only; do not ask for a temporary Cloudflare Tunnel token')
|
|
651
649
|
.option('--web', 'open Remote Config through the public tunnel for at most one hour', false)
|
|
652
650
|
.option('--quiet', 'suppress the startup banner', false)
|
|
653
651
|
.action(async (opts) => {
|
|
@@ -771,8 +769,8 @@ program
|
|
|
771
769
|
console.log('');
|
|
772
770
|
console.log('Next steps:');
|
|
773
771
|
console.log(` 1. Point your own tunnel at 127.0.0.1:${next.port} for host ${url.hostname} (see docs/TUNNEL.md — route ALL paths, not just /mcp)`);
|
|
774
|
-
console.log(
|
|
775
|
-
console.log(' 3.
|
|
772
|
+
console.log(` 2. Select it persistently: dodo tunnel configure --tunnel --public-url ${url.origin} --os-credential`);
|
|
773
|
+
console.log(' 3. Register your client callback: dodo auth add-client --redirect-uri <exact callback URL from your client UI>');
|
|
776
774
|
console.log(' 4. dodo start');
|
|
777
775
|
}
|
|
778
776
|
catch (err) {
|
|
@@ -1440,10 +1438,12 @@ Desktop (platform helper and explicit app permission required):
|
|
|
1440
1438
|
dodo desktop disable revoke desktop access
|
|
1441
1439
|
Remote setup:
|
|
1442
1440
|
dodo init --public-url https://... configure public origin once
|
|
1443
|
-
dodo
|
|
1441
|
+
dodo tunnel configure --tunnel --os-credential --public-url https://...
|
|
1442
|
+
select the persistent DODO Tunnel
|
|
1443
|
+
dodo tunnel configure --local select local-only MCP
|
|
1444
|
+
dodo start use exactly the saved connection mode
|
|
1444
1445
|
dodo --web start/renew Remote Config through that tunnel for 1 hour
|
|
1445
|
-
dodo web --close close Remote Config; keep MCP/Tunnel running
|
|
1446
|
-
dodo start --no-tunnel run local MCP only`);
|
|
1446
|
+
dodo web --close close Remote Config; keep MCP/Tunnel running`);
|
|
1447
1447
|
function effectiveArgv() {
|
|
1448
1448
|
if (process.argv.length <= 2)
|
|
1449
1449
|
return [...process.argv, 'start'];
|