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.
- package/README.md +31 -18
- package/dist/cli/main.js +63 -66
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/menu.js +9 -16
- 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/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 +6 -2
- package/docs/MANUAL_ACCEPTANCE.md +21 -16
- package/docs/RELEASE_1.0.0.md +3 -0
- package/docs/RELEASE_1.0.3.md +57 -0
- package/docs/RELEASE_NOTES.md +19 -0
- package/docs/SECURITY.md +20 -10
- package/docs/TEST_REPORT.md +33 -1
- package/docs/TUNNEL.md +102 -80
- package/docs/WINDOWS.md +1 -1
- 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 +2 -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.3** คือ MCP server แบบ local-first สำหรับให้ AI ช่วยพัฒนา software โดยทำงานกับ workspace ที่เจ้าของเลือก ค่าเริ่มต้นเป็นโหมดส่วนตัวแบบเพิ่มโปรเจกต์แล้วใช้ได้ทันที และยังมีโหมด managed สำหรับแยก workspace ACL/trust แบบละเอียด
|
|
4
4
|
|
|
5
5
|
## จุดเด่น
|
|
6
6
|
|
|
@@ -250,20 +250,34 @@ command sandbox, idempotency และ audit เดิม `agent_exec` ใช้
|
|
|
250
250
|
|
|
251
251
|
### เชื่อม Remote MCP ผ่าน Cloudflare Tunnel
|
|
252
252
|
|
|
253
|
-
DODO
|
|
253
|
+
DODO เลือกวิธีเชื่อมต่อระดับ installation ได้หนึ่งแบบ: `local` หรือ `tunnel` ค่าที่เลือก
|
|
254
|
+
มีผลกับทุก `dodo start` และไม่มี fallback อัตโนมัติ หากเลือก Tunnel แล้ว credential หรือ
|
|
255
|
+
readiness ไม่พร้อม DODO จะหยุด startup แทนการเปิด local โดยไม่แจ้ง
|
|
256
|
+
|
|
257
|
+
Local mode:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
dodo tunnel configure --local
|
|
261
|
+
dodo start
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
DODO Tunnel mode:
|
|
254
265
|
|
|
255
266
|
```bash
|
|
256
267
|
dodo setup --check --components cloudflared
|
|
257
|
-
dodo
|
|
268
|
+
dodo tunnel configure \
|
|
269
|
+
--tunnel \
|
|
270
|
+
--public-url https://mcp.example.com \
|
|
271
|
+
--os-credential
|
|
258
272
|
dodo start
|
|
259
|
-
# Cloudflare Tunnel token (temporary; Enter = local only):
|
|
260
273
|
```
|
|
261
274
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
275
|
+
เจ้าของสร้าง remotely-managed Tunnel, public hostname และ DNS ใน Cloudflare และ route
|
|
276
|
+
**ทุก path** มาที่ `http://127.0.0.1:21730` DODO ไม่จัดการ Cloudflare account/DNS
|
|
277
|
+
Token ถูกบันทึกใน macOS Keychain, Windows Credential Manager หรือ Linux Secret Service;
|
|
278
|
+
global config เก็บเพียง opaque reference ทุกครั้งที่เปิด DODO ระบบจะเริ่ม
|
|
279
|
+
`cloudflared` เป็น child และหยุด child พร้อม DODO Token ไม่อยู่ใน argv, config, log,
|
|
280
|
+
audit, browser storage, MCP response หรือ environment ของ MCP jobs
|
|
267
281
|
|
|
268
282
|
หากต้องตั้งค่าจากอุปกรณ์อื่น ให้เปิดหน้าเจ้าของแบบชั่วคราวผ่าน public listener เดียวกัน:
|
|
269
283
|
|
|
@@ -277,15 +291,14 @@ dodo web --close # ปิดหน้า Remote Config แต่ MCP/Tunnel
|
|
|
277
291
|
terminal หลังจับคู่ browser จะใช้ cookie ที่เป็น `Secure`, `HttpOnly`,
|
|
278
292
|
`SameSite=Strict` และจำกัด path ที่ `/config` หน้า Remote Config ปิดอัตโนมัติภายใน
|
|
279
293
|
1 ชั่วโมง การเปิดใหม่ออก code/session ใหม่และไม่ต้อง restart MCP ไม่มี token หรือ
|
|
280
|
-
credential อยู่ใน URL
|
|
281
|
-
token
|
|
282
|
-
|
|
283
|
-
หน้า Local Config ที่ `127.0.0.1:21731`
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
`dodo tunnel
|
|
287
|
-
`dodo tunnel
|
|
288
|
-
โดยเจ้าของอย่างชัดเจน; เส้นทาง `dodo start` และหน้าเว็บไม่ใช้ credential store
|
|
294
|
+
credential อยู่ใน URL คำสั่งนี้ใช้ได้เฉพาะเมื่อ persistent mode เป็น `tunnel` และ
|
|
295
|
+
DODO-owned Tunnel กำลังทำงาน; มันไม่รับ token ใหม่ผ่าน IPC และไม่เปลี่ยนจาก Local เอง
|
|
296
|
+
|
|
297
|
+
หน้า Local Config ที่ `127.0.0.1:21731` เลือก Local/Tunnel, ตั้ง public origin และบันทึก
|
|
298
|
+
token แบบ write-only เข้า OS credential store ได้ ค่าใหม่มีผลหลัง restart หน้าเว็บแสดง
|
|
299
|
+
Active MCP URL จาก runtime จริงและไม่กล่าวว่า Tunnel หรือ AI client เชื่อมแล้วหากไม่มี
|
|
300
|
+
หลักฐาน ดูสถานะด้วย `dodo tunnel status`, ตรวจ connectivity ด้วย `dodo tunnel doctor`
|
|
301
|
+
และดู log ที่ redacted ด้วย `dodo tunnel logs`
|
|
289
302
|
|
|
290
303
|
บน macOS `dodo setup --yes` เลือก component `cloudflared` อยู่ในชุด `all` แล้ว
|
|
291
304
|
Tunnel route ต้องชี้เฉพาะ MCP/OAuth listener `21730` ห้ามชี้ Local Config `21731`
|
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)
|
|
@@ -367,9 +362,12 @@ project.command('remove <projectId>')
|
|
|
367
362
|
// --------------------------------------------------------------- tunnel ----
|
|
368
363
|
const tunnel = program.command('tunnel').description('manage or inspect a Cloudflare remotely-managed Tunnel without managing DNS or the Cloudflare account');
|
|
369
364
|
tunnel.command('configure')
|
|
370
|
-
.description('
|
|
371
|
-
.option('--
|
|
372
|
-
.option('--
|
|
365
|
+
.description('select persistent local or DODO-owned Tunnel mode; tokens are never written to config')
|
|
366
|
+
.option('--tunnel', 'select the DODO-owned Tunnel for every dodo start', false)
|
|
367
|
+
.option('--local', 'select local-only MCP for every dodo start', false)
|
|
368
|
+
.option('--managed', 'deprecated alias for --tunnel', false)
|
|
369
|
+
.option('--external', 'deprecated alias for --local', false)
|
|
370
|
+
.option('--public-url <url>', 'public HTTPS origin used by the selected DODO Tunnel')
|
|
373
371
|
.option('--os-credential', 'prompt through the reviewed OS credential provider', false)
|
|
374
372
|
.option('--token-env <name>', 'reference an uppercase environment variable that already contains the token')
|
|
375
373
|
.option('--token-file <absolute-path>', 'reference an owner-private absolute token file')
|
|
@@ -381,8 +379,10 @@ tunnel.command('configure')
|
|
|
381
379
|
.option('--json', 'machine-readable non-secret result', false)
|
|
382
380
|
.action(async (opts) => {
|
|
383
381
|
try {
|
|
384
|
-
|
|
385
|
-
|
|
382
|
+
const chooseTunnel = opts.tunnel || opts.managed;
|
|
383
|
+
const chooseLocal = opts.local || opts.external;
|
|
384
|
+
if (chooseTunnel && chooseLocal)
|
|
385
|
+
fail('choose --tunnel or --local');
|
|
386
386
|
const methods = [opts.osCredential, opts.tokenEnv !== undefined, opts.tokenFile !== undefined].filter(Boolean).length;
|
|
387
387
|
if (methods > 1)
|
|
388
388
|
fail('choose only one of --os-credential, --token-env or --token-file');
|
|
@@ -393,6 +393,16 @@ tunnel.command('configure')
|
|
|
393
393
|
const { dir } = resolveConfigDir(process.env);
|
|
394
394
|
ensureConfigDir(dir);
|
|
395
395
|
const paths = statePaths(dir), current = loadGlobalConfig(paths.configFile);
|
|
396
|
+
const connectionMode = chooseTunnel ? 'tunnel' : chooseLocal ? 'local' : methods > 0 ? 'tunnel' : current.tunnel.connectionMode;
|
|
397
|
+
const publicUrl = opts.publicUrl !== undefined
|
|
398
|
+
? validatePublicUrl(opts.publicUrl, false).origin
|
|
399
|
+
: current.publicUrl;
|
|
400
|
+
if (connectionMode === 'tunnel' && !publicUrl)
|
|
401
|
+
fail('Tunnel mode requires --public-url https://your-host (or a previously saved public origin)');
|
|
402
|
+
if (connectionMode === 'tunnel' && publicUrl)
|
|
403
|
+
validatePublicUrl(publicUrl, false);
|
|
404
|
+
if (connectionMode === 'local' && opts.publicUrl !== undefined)
|
|
405
|
+
fail('--public-url is used only with --tunnel');
|
|
396
406
|
const credentials = await import('../tunnel/credentials.js');
|
|
397
407
|
const { resolveCloudflared } = await import('../tunnel/supervisor.js');
|
|
398
408
|
const previousCredentialRef = current.tunnel.credentialRef;
|
|
@@ -401,7 +411,7 @@ tunnel.command('configure')
|
|
|
401
411
|
credentialRef = undefined;
|
|
402
412
|
const baseTunnelInput = {
|
|
403
413
|
...current.tunnel,
|
|
404
|
-
|
|
414
|
+
connectionMode,
|
|
405
415
|
...(credentialRef ? { credentialRef } : {}),
|
|
406
416
|
...(opts.metricsPort !== undefined ? { metricsPort: opts.metricsPort } : {}),
|
|
407
417
|
...(opts.maxRestarts !== undefined ? { maxRestarts: opts.maxRestarts } : {}),
|
|
@@ -409,7 +419,7 @@ tunnel.command('configure')
|
|
|
409
419
|
};
|
|
410
420
|
if (!credentialRef)
|
|
411
421
|
delete baseTunnelInput['credentialRef'];
|
|
412
|
-
let next = GlobalConfigSchema.parse({ ...current, tunnel: baseTunnelInput });
|
|
422
|
+
let next = GlobalConfigSchema.parse({ ...current, ...(publicUrl ? { publicUrl } : {}), tunnel: baseTunnelInput });
|
|
413
423
|
if (opts.cloudflared !== undefined) {
|
|
414
424
|
const canonical = resolveCloudflared(next);
|
|
415
425
|
next = GlobalConfigSchema.parse({ ...next, tunnel: { ...next.tunnel, executable: canonical } });
|
|
@@ -427,19 +437,19 @@ tunnel.command('configure')
|
|
|
427
437
|
if (!credentialRef)
|
|
428
438
|
delete withCredential['credentialRef'];
|
|
429
439
|
next = GlobalConfigSchema.parse({ ...next, tunnel: withCredential });
|
|
430
|
-
if (next.tunnel.
|
|
431
|
-
fail('
|
|
440
|
+
if (next.tunnel.connectionMode === 'tunnel' && !next.tunnel.credentialRef)
|
|
441
|
+
fail('Tunnel mode requires --os-credential, --token-env or --token-file');
|
|
432
442
|
saveGlobalConfig(paths.configFile, next);
|
|
433
443
|
if (opts.removeCredential && previousCredentialRef)
|
|
434
444
|
credentials.deleteOsTunnelCredential(previousCredentialRef);
|
|
435
|
-
const result = {
|
|
445
|
+
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
446
|
if (opts.json)
|
|
437
447
|
console.log(JSON.stringify(result, null, 2));
|
|
438
448
|
else {
|
|
439
|
-
console.log(`
|
|
449
|
+
console.log(`Connection mode: ${result.connectionMode}`);
|
|
440
450
|
console.log(`Credential: ${result.credential} (value is not stored in config or displayed)`);
|
|
441
451
|
console.log(`Readiness: 127.0.0.1:${result.metricsPort} Restarts: ${result.maxRestarts}`);
|
|
442
|
-
console.log(result.
|
|
452
|
+
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
453
|
}
|
|
444
454
|
}
|
|
445
455
|
catch (error) {
|
|
@@ -570,7 +580,7 @@ program.command('cli')
|
|
|
570
580
|
startupProject,
|
|
571
581
|
selectProject: selectStartupProject,
|
|
572
582
|
addProject: addAndSelectProject,
|
|
573
|
-
start: async (root
|
|
583
|
+
start: async (root) => launchHttp({ ...(root === undefined ? {} : { root }) }),
|
|
574
584
|
openRemoteConfig: openRemoteConfigFromCli,
|
|
575
585
|
setupAll: () => setupFromMenu(false),
|
|
576
586
|
checkSetup: () => setupFromMenu(true),
|
|
@@ -590,20 +600,7 @@ function printRemoteConfig(result) {
|
|
|
590
600
|
}
|
|
591
601
|
async function openRemoteConfigFromCli() {
|
|
592
602
|
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
|
-
}
|
|
603
|
+
printRemoteConfig(await ipcForInstallation('remoteConfig.open'));
|
|
607
604
|
}
|
|
608
605
|
catch (error) {
|
|
609
606
|
if (!(error instanceof IpcError) || !error.message.includes('no running DODO installation'))
|
|
@@ -641,13 +638,11 @@ program
|
|
|
641
638
|
.description('start the MCP server for the saved project, or wait for a project selection (foreground)')
|
|
642
639
|
.option('--root <path>', 'select and remember an explicit workspace root (local CLI only)')
|
|
643
640
|
.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
641
|
.option('--allow-unsafe-root', 'permit filesystem root / home / other unusually broad workspace roots', false)
|
|
646
642
|
.option('--allow', 'allow task execution for this run (requires --all)', false)
|
|
647
643
|
.option('--all', 'all local action classes (requires --allow)', false)
|
|
648
644
|
.option('--bypass', 'trusted actions and no default job sandbox for this run; OAuth and file guards remain', false)
|
|
649
645
|
.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
646
|
.option('--web', 'open Remote Config through the public tunnel for at most one hour', false)
|
|
652
647
|
.option('--quiet', 'suppress the startup banner', false)
|
|
653
648
|
.action(async (opts) => {
|
|
@@ -771,8 +766,8 @@ program
|
|
|
771
766
|
console.log('');
|
|
772
767
|
console.log('Next steps:');
|
|
773
768
|
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.
|
|
769
|
+
console.log(` 2. Select it persistently: dodo tunnel configure --tunnel --public-url ${url.origin} --os-credential`);
|
|
770
|
+
console.log(' 3. Register your client callback: dodo auth add-client --redirect-uri <exact callback URL from your client UI>');
|
|
776
771
|
console.log(' 4. dodo start');
|
|
777
772
|
}
|
|
778
773
|
catch (err) {
|
|
@@ -1440,10 +1435,12 @@ Desktop (platform helper and explicit app permission required):
|
|
|
1440
1435
|
dodo desktop disable revoke desktop access
|
|
1441
1436
|
Remote setup:
|
|
1442
1437
|
dodo init --public-url https://... configure public origin once
|
|
1443
|
-
dodo
|
|
1438
|
+
dodo tunnel configure --tunnel --os-credential --public-url https://...
|
|
1439
|
+
select the persistent DODO Tunnel
|
|
1440
|
+
dodo tunnel configure --local select local-only MCP
|
|
1441
|
+
dodo start use exactly the saved connection mode
|
|
1444
1442
|
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`);
|
|
1443
|
+
dodo web --close close Remote Config; keep MCP/Tunnel running`);
|
|
1447
1444
|
function effectiveArgv() {
|
|
1448
1445
|
if (process.argv.length <= 2)
|
|
1449
1446
|
return [...process.argv, 'start'];
|