dodo-mcp 1.0.0 → 1.0.2

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.
@@ -0,0 +1,317 @@
1
+ import http from 'node:http';
2
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
3
+ import express from 'express';
4
+ import { DodoError } from '../errors.js';
5
+ const DEFAULT_LEASE_MS = 60 * 60 * 1000;
6
+ const PAIRING_TTL_MS = 10 * 60 * 1000;
7
+ const COOKIE_NAME = '__Secure-dodo_remote_config';
8
+ const REMOTE_CSP = "default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'none'; form-action 'self'";
9
+ const PAIR_ALPHABET = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
10
+ function digest(value) {
11
+ return createHash('sha256').update(value).digest();
12
+ }
13
+ function sameDigest(expected, value) {
14
+ if (!expected)
15
+ return false;
16
+ const actual = digest(value);
17
+ return actual.length === expected.length && timingSafeEqual(actual, expected);
18
+ }
19
+ function pairingCode() {
20
+ const bytes = randomBytes(16);
21
+ const chars = [...bytes].map(byte => PAIR_ALPHABET[byte & 31]).join('');
22
+ return chars.match(/.{1,4}/g)?.join('-') ?? chars;
23
+ }
24
+ function cookie(req, name) {
25
+ const raw = req.headers.cookie ?? '';
26
+ for (const part of raw.split(';')) {
27
+ const item = part.trim();
28
+ if (!item.startsWith(`${name}=`))
29
+ continue;
30
+ const value = item.slice(name.length + 1);
31
+ if (/^[A-Za-z0-9_-]{20,200}$/.test(value))
32
+ return value;
33
+ }
34
+ return '';
35
+ }
36
+ function securityHeaders(res) {
37
+ res.setHeader('Cache-Control', 'no-store');
38
+ res.setHeader('Pragma', 'no-cache');
39
+ res.setHeader('Referrer-Policy', 'no-referrer');
40
+ res.setHeader('X-Content-Type-Options', 'nosniff');
41
+ res.setHeader('X-Frame-Options', 'DENY');
42
+ res.setHeader('Content-Security-Policy', REMOTE_CSP);
43
+ }
44
+ function safeHeader(req, name, max) {
45
+ const value = req.headers[name];
46
+ if (typeof value !== 'string' || value.length === 0 || value.length > max || /[\r\n]/.test(value))
47
+ return undefined;
48
+ return value;
49
+ }
50
+ const LOGIN_HTML = Buffer.from(`<!doctype html>
51
+ <html lang="th" data-theme="dark"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="color-scheme" content="dark light"><meta name="referrer" content="no-referrer"><title>DODO Remote Config</title><link rel="stylesheet" href="/config/login.css"></head><body><main><section><div class="logo" aria-hidden="true">DD</div><h1>DODO Remote Config</h1><p>หน้าเจ้าของเครื่องแบบชั่วคราว กรุณากรอก pairing code ที่แสดงใน terminal ของ DODO</p><form id="pair" novalidate><label for="code">Pairing code</label><input id="code" name="code" type="text" autocomplete="one-time-code" spellcheck="false" maxlength="32" required autofocus><button type="submit">เชื่อมต่อ</button><p id="status" role="alert" aria-live="polite"></p></form><p class="note">Code ใช้ได้ครั้งเดียว หน้า Config จะปิดอัตโนมัติภายใน 1 ชั่วโมง และไม่มี credential อยู่ใน URL</p></section></main><script src="/config/login.js"></script></body></html>`);
52
+ const LOGIN_CSS = Buffer.from(`:root{color-scheme:dark;background:#0f141b;color:#e8edf4;font:16px/1.6 system-ui,-apple-system,"Noto Sans Thai",sans-serif}*{box-sizing:border-box}body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px}main{width:min(100%,480px)}section{background:#171e28;border:1px solid #303a48;border-radius:14px;padding:28px;box-shadow:0 18px 45px #0005}.logo{display:grid;place-items:center;width:48px;height:48px;border-radius:10px;background:#2c78c8;color:white;font-weight:800}h1{margin:18px 0 4px;font-size:1.55rem}p{color:#b7c1ce}label{display:block;margin:20px 0 7px;font-weight:700}input,button{width:100%;min-height:46px;border-radius:9px;font:inherit}input{border:1px solid #526074;background:#0e141c;color:#fff;padding:10px 12px;text-transform:uppercase;letter-spacing:.08em}input:focus{outline:3px solid #4c9cff55;border-color:#65aaff}button{margin-top:12px;border:0;background:#2877c7;color:white;font-weight:750;cursor:pointer}button:disabled{opacity:.6;cursor:wait}#status{min-height:1.6em;color:#ff9e9e}.note{font-size:.9rem;color:#93a0b1}@media(prefers-color-scheme:light){:root{background:#eef2f7;color:#17202b}section{background:#fff;border-color:#ccd5e1;box-shadow:0 18px 45px #34405422}p{color:#566274}input{background:#fff;color:#17202b;border-color:#8794a6}.note{color:#657286}}`);
53
+ const LOGIN_JS = Buffer.from(`'use strict';(()=>{const form=document.getElementById('pair'),input=document.getElementById('code'),status=document.getElementById('status'),button=form.querySelector('button');form.addEventListener('submit',async event=>{event.preventDefault();if(button.disabled)return;status.textContent='';button.disabled=true;button.textContent='กำลังตรวจ…';try{const response=await fetch('/config/pair',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({code:input.value}),credentials:'same-origin',cache:'no-store'});let result={};try{result=await response.json()}catch{}if(!response.ok)throw new Error(result.error||('HTTP '+response.status));input.value='';location.replace('/config');}catch(error){input.value='';status.textContent='✕ '+error.message;input.focus();}finally{button.disabled=false;button.textContent='เชื่อมต่อ';}});})();`);
54
+ /**
55
+ * A process-only, one-hour bridge from the public listener to the existing
56
+ * loopback Local Config server. It grants no authority itself: after a
57
+ * one-time pairing exchange every request is re-authenticated here and then
58
+ * forwarded through Local Config's original capability and policy routes.
59
+ */
60
+ export class RemoteConfigGateway {
61
+ publicOrigin;
62
+ defaultLeaseMs;
63
+ target;
64
+ pairingHash;
65
+ pairingExpiresAt = 0;
66
+ sessionHash;
67
+ expiresAt = 0;
68
+ attempts = 0;
69
+ attemptWindow = 0;
70
+ timer;
71
+ constructor(publicOrigin, defaultLeaseMs = DEFAULT_LEASE_MS) {
72
+ this.publicOrigin = publicOrigin;
73
+ this.defaultLeaseMs = defaultLeaseMs;
74
+ }
75
+ attachLocal(url) {
76
+ const parsed = new URL(url);
77
+ const capability = parsed.hash.slice(1);
78
+ if (parsed.protocol !== 'http:' || parsed.hostname !== '127.0.0.1' || !capability) {
79
+ throw new DodoError('INTERNAL_ERROR', 'Local Config target is not an owner-private loopback capability');
80
+ }
81
+ parsed.hash = '';
82
+ this.target = { origin: parsed.origin, capability };
83
+ }
84
+ open(leaseMs = this.defaultLeaseMs) {
85
+ if (!this.target)
86
+ throw new DodoError('NOT_SUPPORTED', 'Local Config is unavailable for this DODO process');
87
+ if (!Number.isSafeInteger(leaseMs) || leaseMs < 100 || leaseMs > DEFAULT_LEASE_MS) {
88
+ throw new DodoError('INVALID_INPUT', 'Remote Config lifetime must not exceed 1 hour');
89
+ }
90
+ this.close();
91
+ const code = pairingCode();
92
+ this.pairingHash = digest(code);
93
+ this.expiresAt = Date.now() + leaseMs;
94
+ this.pairingExpiresAt = Math.min(this.expiresAt, Date.now() + PAIRING_TTL_MS);
95
+ this.attemptWindow = Date.now();
96
+ this.timer = setTimeout(() => this.close(), leaseMs);
97
+ this.timer.unref();
98
+ return { url: `${this.publicOrigin}/config`, pairingCode: code, expiresAt: this.expiresAt };
99
+ }
100
+ close() {
101
+ if (this.timer)
102
+ clearTimeout(this.timer);
103
+ this.timer = undefined;
104
+ this.pairingHash = undefined;
105
+ this.sessionHash = undefined;
106
+ this.pairingExpiresAt = 0;
107
+ this.expiresAt = 0;
108
+ this.attempts = 0;
109
+ }
110
+ status() {
111
+ const active = this.active();
112
+ return {
113
+ active,
114
+ paired: active && Boolean(this.sessionHash),
115
+ url: `${this.publicOrigin}/config`,
116
+ expiresAt: active ? this.expiresAt : null,
117
+ };
118
+ }
119
+ mount(app) {
120
+ app.get('/config/login.css', (req, res, next) => this.publicAsset(req, res, next, 'text/css; charset=utf-8', LOGIN_CSS));
121
+ app.get('/config/login.js', (req, res, next) => this.publicAsset(req, res, next, 'text/javascript; charset=utf-8', LOGIN_JS));
122
+ app.post('/config/pair', express.json({ limit: 2048 }), (req, res, next) => this.pair(req, res, next));
123
+ app.use('/config/pair', (error, _req, res, _next) => {
124
+ if (!this.active()) {
125
+ res.status(404).end();
126
+ return;
127
+ }
128
+ securityHeaders(res);
129
+ res.status(400).json({ error: 'Pairing request is invalid', code: 'INVALID_INPUT' });
130
+ void error;
131
+ });
132
+ app.get(['/config', '/config/'], (req, res, next) => {
133
+ if (!this.active()) {
134
+ next();
135
+ return;
136
+ }
137
+ if (!this.authorized(req)) {
138
+ securityHeaders(res);
139
+ res.type('html').send(LOGIN_HTML);
140
+ return;
141
+ }
142
+ this.proxy(req, res, '/', true);
143
+ });
144
+ app.use('/config/assets', (req, res, next) => {
145
+ if (!this.active()) {
146
+ next();
147
+ return;
148
+ }
149
+ if (!this.authorized(req)) {
150
+ securityHeaders(res);
151
+ res.status(401).end();
152
+ return;
153
+ }
154
+ if (req.method !== 'GET') {
155
+ res.status(405).end();
156
+ return;
157
+ }
158
+ this.proxy(req, res, req.originalUrl.slice('/config'.length), false);
159
+ });
160
+ app.use('/config/api', (req, res, next) => {
161
+ if (!this.active()) {
162
+ next();
163
+ return;
164
+ }
165
+ if (!this.authorized(req)) {
166
+ securityHeaders(res);
167
+ res.status(401).json({ error: 'Remote Config requires pairing from the owner terminal', code: 'REMOTE_CONFIG_AUTH_REQUIRED' });
168
+ return;
169
+ }
170
+ if (!['GET', 'POST'].includes(req.method)) {
171
+ res.status(405).end();
172
+ return;
173
+ }
174
+ const origin = req.headers.origin;
175
+ if ((origin && origin !== this.publicOrigin) || req.headers['sec-fetch-site'] === 'cross-site') {
176
+ securityHeaders(res);
177
+ res.status(403).end();
178
+ return;
179
+ }
180
+ this.proxy(req, res, req.originalUrl.slice('/config'.length), req.method === 'GET' && req.path === '/state');
181
+ });
182
+ }
183
+ active() {
184
+ if (this.expiresAt > Date.now())
185
+ return true;
186
+ if (this.expiresAt !== 0)
187
+ this.close();
188
+ return false;
189
+ }
190
+ authorized(req) {
191
+ return this.active() && sameDigest(this.sessionHash, cookie(req, COOKIE_NAME));
192
+ }
193
+ publicAsset(req, res, next, type, body) {
194
+ if (!this.active()) {
195
+ next();
196
+ return;
197
+ }
198
+ securityHeaders(res);
199
+ res.setHeader('Content-Type', type);
200
+ res.send(body);
201
+ }
202
+ pair(req, res, next) {
203
+ if (!this.active()) {
204
+ next();
205
+ return;
206
+ }
207
+ securityHeaders(res);
208
+ const origin = req.headers.origin;
209
+ if ((origin && origin !== this.publicOrigin) || req.headers['sec-fetch-site'] === 'cross-site') {
210
+ res.status(403).end();
211
+ return;
212
+ }
213
+ const now = Date.now();
214
+ if (now - this.attemptWindow >= 60_000) {
215
+ this.attemptWindow = now;
216
+ this.attempts = 0;
217
+ }
218
+ this.attempts += 1;
219
+ if (this.attempts > 10) {
220
+ res.status(429).json({ error: 'Too many pairing attempts; run dodo --web to issue a new code', code: 'RATE_LIMITED' });
221
+ return;
222
+ }
223
+ const code = req.body && typeof req.body.code === 'string' ? req.body.code.trim().toUpperCase() : '';
224
+ if (now > this.pairingExpiresAt || !sameDigest(this.pairingHash, code)) {
225
+ res.status(401).json({ error: 'Pairing code is invalid or expired', code: 'INVALID_PAIRING_CODE' });
226
+ return;
227
+ }
228
+ this.pairingHash = undefined; // one-time exchange
229
+ const session = randomBytes(32).toString('base64url');
230
+ this.sessionHash = digest(session);
231
+ const maxAge = Math.max(1, Math.floor((this.expiresAt - now) / 1000));
232
+ res.setHeader('Set-Cookie', `${COOKIE_NAME}=${session}; Path=/config; Max-Age=${maxAge}; HttpOnly; Secure; SameSite=Strict`);
233
+ res.json({ ok: true, expiresAt: this.expiresAt, redirect: '/config' });
234
+ }
235
+ proxy(req, res, upstreamPath, transform) {
236
+ const target = this.target;
237
+ if (!target) {
238
+ securityHeaders(res);
239
+ res.status(503).json({ error: 'Local Config is unavailable' });
240
+ return;
241
+ }
242
+ const local = new URL(target.origin);
243
+ const headers = {
244
+ Host: local.host,
245
+ Authorization: `Bearer ${target.capability}`,
246
+ };
247
+ for (const [name, max] of [['content-type', 100], ['x-dodo-workspace', 256], ['x-dodo-epoch', 256]]) {
248
+ const value = safeHeader(req, name, max);
249
+ if (value)
250
+ headers[name] = value;
251
+ }
252
+ const length = safeHeader(req, 'content-length', 20);
253
+ if (length && /^\d+$/.test(length) && Number(length) <= 32 * 1024)
254
+ headers['content-length'] = length;
255
+ const upstream = http.request({
256
+ hostname: '127.0.0.1',
257
+ port: Number(local.port),
258
+ path: upstreamPath,
259
+ method: req.method,
260
+ headers,
261
+ }, response => {
262
+ const status = response.statusCode ?? 502;
263
+ const type = typeof response.headers['content-type'] === 'string' ? response.headers['content-type'] : undefined;
264
+ securityHeaders(res);
265
+ res.status(status);
266
+ if (type)
267
+ res.setHeader('Content-Type', type);
268
+ if (!transform || status !== 200) {
269
+ response.pipe(res);
270
+ return;
271
+ }
272
+ const chunks = [];
273
+ let size = 0;
274
+ response.on('data', (chunk) => {
275
+ size += chunk.length;
276
+ if (size <= 2 * 1024 * 1024)
277
+ chunks.push(chunk);
278
+ });
279
+ response.on('end', () => {
280
+ if (size > 2 * 1024 * 1024) {
281
+ res.status(502).json({ error: 'Remote Config response exceeded its bound' });
282
+ return;
283
+ }
284
+ let body = Buffer.concat(chunks);
285
+ if (upstreamPath === '/') {
286
+ body = Buffer.from(body.toString('utf8').replaceAll('"/assets/', '"/config/assets/'));
287
+ }
288
+ else if (upstreamPath.startsWith('/api/state')) {
289
+ try {
290
+ const parsed = JSON.parse(body.toString('utf8'));
291
+ parsed.connection ??= {};
292
+ parsed.connection['expiresAt'] = this.expiresAt;
293
+ parsed.connection['remoteConfig'] = { active: true, expiresAt: this.expiresAt };
294
+ body = Buffer.from(JSON.stringify(parsed));
295
+ }
296
+ catch {
297
+ res.status(502).json({ error: 'Local Config returned invalid state' });
298
+ return;
299
+ }
300
+ }
301
+ res.send(body);
302
+ });
303
+ });
304
+ upstream.once('error', () => {
305
+ if (!res.headersSent) {
306
+ securityHeaders(res);
307
+ res.status(502).json({ error: 'Local Config bridge is unavailable' });
308
+ }
309
+ else
310
+ res.end();
311
+ });
312
+ req.once('aborted', () => upstream.destroy());
313
+ req.pipe(upstream);
314
+ }
315
+ }
316
+ export { DEFAULT_LEASE_MS as REMOTE_CONFIG_LEASE_MS };
317
+ //# sourceMappingURL=remoteConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remoteConfig.js","sourceRoot":"","sources":["../../src/server/remoteConfig.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  # AI Providers, Sub-agents และหลายโปรเจกต์
2
2
 
3
- DODO MCP 1.0.0 สามารถเปิด runtime ของหลายโปรเจกต์ใน process เดียว และเรียกโมเดลจาก backend ผ่าน connection/profile ที่เจ้าของเลือกได้ ค่าเริ่มต้นเป็นโหมดส่วนตัว: เพิ่มโปรเจกต์, connection และ profile แล้วใช้งานได้ทันทีโดยไม่ต้องทำ allowlist ซ้ำ
3
+ DODO MCP 1.0.2 สามารถเปิด runtime ของหลายโปรเจกต์ใน process เดียว และเรียกโมเดลจาก backend ผ่าน connection/profile ที่เจ้าของเลือกได้ ค่าเริ่มต้นเป็นโหมดส่วนตัว: เพิ่มโปรเจกต์, connection และ profile แล้วใช้งานได้ทันทีโดยไม่ต้องทำ allowlist ซ้ำ
4
4
 
5
5
  ## เริ่มใช้งานผ่านเว็บ
6
6
 
@@ -1,8 +1,27 @@
1
1
  # DODO MCP — Architecture
2
2
 
3
+ ## Platform evidence
4
+
5
+ `release-gate.mjs` collects separate core/packaging JSON reports from `test:all`,
6
+ validates their assertion counts, and binds the run to HEAD, dependency lock and
7
+ a fingerprint of code/tests/gate inputs (including untracked candidate files).
8
+ The fingerprint excludes generated dist/schemas and documentation; it is checked
9
+ again at completion. Docker also checks the actual copied inputs against the host
10
+ fingerprint. A dirty candidate remains ineligible for a clean release claim.
11
+
12
+ Private command capture is shared by the release gate and Linux Docker driver.
13
+ `gate-summary.mjs` writes a separate allowlisted public summary; it never rewrites
14
+ the authoritative private report or promotes manual acceptance. Evidence output
15
+ directories cannot reuse old gate/test reports. Linux images require real ffmpeg
16
+ and eSpeak coverage; absence of those prerequisites is a gate failure, while
17
+ Whisper/interactive desktop remain explicitly separate gates.
18
+
3
19
  ## Overview
4
20
 
5
- DODO แบ่งเป็น data plane สำหรับ MCP tools และ owner control plane สำหรับ Local Config/CLI ทั้งสอง plane ใช้ state และ workspace lifecycle เดียวกัน แต่ public MCP ไม่มี admin endpoint
21
+ DODO แบ่งเป็น data plane สำหรับ MCP tools และ owner control plane สำหรับ Local
22
+ Config/CLI ทั้งสอง plane ใช้ state และ workspace lifecycle เดียวกัน Public listener
23
+ ไม่มี owner route ที่ active ตามค่าเริ่มต้น; ADR-047 เพิ่ม bounded `/config` bridge ที่
24
+ เจ้าของเปิดชั่วคราวและปิดเป็น 404 เมื่อ lease หมด
6
25
 
7
26
  ```text
8
27
  AI client
@@ -26,6 +45,10 @@ Owner browser/CLI
26
45
  ▼
27
46
  Local Config 127.0.0.1:21731 ──► WorkspaceHost ──► active workspace lifecycle
28
47
 
48
+ Tunnel ──► 127.0.0.1:21730/config ──► one-time pairing/session gate
49
+ └──────────► Local Config 127.0.0.1:21731
50
+ (lease ไม่เกิน 1 ชั่วโมง)
51
+
29
52
  Local owner CLI ──► authenticated tunnel IPC ──► bounded cloudflared supervisor
30
53
 
31
54
  Local owner CLI/Config ──► project registry ──► projectId + workspace reference
@@ -289,7 +312,15 @@ token ได้จาก hidden terminal prompt ระหว่าง `dodo star
289
312
  Config endpoint Token ไม่เข้า trusted config หรือ OS credential provider Runtime ส่งค่า
290
313
  ผ่าน `TUNNEL_TOKEN` ให้ child โดยตรง เก็บเพียงสถานะที่ไม่มี secret และปิด child ก่อน
291
314
  DODO process จบ การเปลี่ยน workspace ไม่ย้ายหรือเพิ่มสิทธิ์ใด ๆ และ Local Config port
292
- ยังไม่ถูก mount บน MCP/public listener
315
+ 21731 ยังคงไม่รับ traffic จาก Tunnel
316
+
317
+ Remote Config ไม่เปลี่ยน bind ของ Local Config และไม่ copy admin handlers มาที่ public
318
+ app `RemoteConfigGateway` ถือ pairing/session digest กับ expiry ใน memory, proxy เฉพาะ
319
+ `/config`, `/config/assets/*`, `/config/api/*` และส่ง request ไป loopback owner server
320
+ ด้วย internal capability เดิม CLI เปิด/ต่ออายุผ่าน authenticated installation IPC;
321
+ หาก process เริ่มแบบ local-only IPC callback เริ่ม `TunnelRuntime` ด้วย run-scoped token
322
+ ก่อนออก lease การหมดอายุล้าง code/session และทำให้ namespace กลับเป็น 404 โดยไม่หยุด
323
+ MCP/OAuth/Tunnel
293
324
 
294
325
  ## Optional services
295
326
 
@@ -10,7 +10,7 @@
10
10
  | Windows | Deferred — มี candidate code/tests แต่ยังไม่ประกาศ native support จนกว่าจะผ่าน Windows 11 จริง |
11
11
  | MCP HTTP | OAuth protected MCP endpoint ที่ `/mcp` |
12
12
  | MCP STDIO | Full surface เป็นค่าเริ่มต้น |
13
- | Local Config | loopback owner control เท่านั้น |
13
+ | Local Config | loopback owner control ที่ 21731; เปิด bounded Remote Config `/config` ผ่าน 21730 ได้ครั้งละไม่เกิน 1 ชั่วโมง |
14
14
  | Cloudflare Tunnel | `dodo start`/Local Config ใช้ run-scoped token และ child `cloudflared`; Tunnel จริงยังเป็น manual gate |
15
15
  | Package | `dodo-mcp` |
16
16
  | CLI | `dodo` |
@@ -4,7 +4,12 @@
4
4
 
5
5
  สถานะล่าสุดของ owner-state setup/import gate: `MANUAL_NOT_RUN` (2026-09-14) ชุด automated ใช้ fixture แยกและไม่แตะ config/OAuth/tunnel ของผู้ใช้
6
6
 
7
- สถานะ Cloudflare Tunnel จริง: `MANUAL_NOT_RUN` (2026-09-14) automated tests ใช้ fake executable และ loopback readiness fixture เท่านั้น ไม่มี Tunnel token, Cloudflare connection, DNS หรือ firewall ใดถูกใช้
7
+ สถานะ live Cloudflare connection smoke: `MANUAL_PASS` (2026-09-15) บน macOS arm64,
8
+ Linux x64 และ Windows x64 โดยใช้ run-scoped secret ชั่วคราว; ทั้งสาม platform รายงาน
9
+ connected, stability 3 วินาที, clean stop และ credentialPersisted=false ใน
10
+ [GitHub run 34908481066](https://github.com/arthittakun/dodo-mcp/actions/runs/34908481066)
11
+ การตรวจ public DNS → MCP/OAuth และ Remote Config ผ่าน hostname จริงยัง
12
+ `MANUAL_NOT_RUN`; connection smoke ไม่ได้อ้างว่า AI client เชื่อมต่อแล้ว
8
13
 
9
14
  ## Cloudflare Tunnel
10
15
 
@@ -21,6 +26,25 @@
21
26
  11. เริ่ม tunnel ใหม่แล้วหยุด DODO; ยืนยัน child หยุดตาม
22
27
  12. บันทึก macOS และ Linux Docker แยก environment; Windows คง `MANUAL_NOT_RUN` จนถึง phase สุดท้าย
23
28
 
29
+ ## Remote Config ผ่าน Tunnel
30
+
31
+ Automated Chromium fixture ผ่านแล้วทั้ง 1440×900 และ 390×844 โดยใช้ isolated
32
+ loopback public-origin fixture: จับคู่, dashboard/assets/API, workspace-bound mutation,
33
+ หมดอายุและปิดเป็น 404 ทำงานจริง การตรวจผ่าน public Cloudflare hostname จริงยัง
34
+ `MANUAL_NOT_RUN`
35
+
36
+ 1. เริ่ม local-only ด้วย `dodo start --no-tunnel`
37
+ 2. จาก terminal อื่นรัน `dodo --web` และกรอก run-scoped Tunnel token แบบซ่อน
38
+ 3. ตรวจว่า process เดิมไม่ restart, MCP/OAuth ยังใช้ epoch เดิม และ cloudflared เริ่มใน process เดิม
39
+ 4. เปิด URL `/config` จากอีกอุปกรณ์ ตรวจว่า URL ไม่มี query/fragment secret
40
+ 5. กรอก pairing code ครั้งเดียว; ใช้ซ้ำต้องถูกปฏิเสธ
41
+ 6. ตรวจ browser cookie เป็น Secure/HttpOnly/SameSite=Strict และ Path `/config`
42
+ 7. เปลี่ยนค่าที่ไม่กระทบข้อมูลจริงใน fixture แล้วตรวจ workspace ID/epoch binding
43
+ 8. รัน `dodo --web` อีกครั้ง ตรวจ session เดิมถูกยกเลิกและได้ code ใหม่
44
+ 9. รัน `dodo web --close` ตรวจ `/config`, assets และ API เป็น 404 แต่ `/healthz`, OAuth และ `/mcp` ยังอยู่
45
+ 10. เปิดใหม่แล้วรอ 1 ชั่วโมง ตรวจ namespace ปิดเองโดยไม่หยุด MCP/Tunnel
46
+ 11. ตรวจ config, audit, tunnel log, browser local/session storage และ process argv ว่าไม่มี Tunnel token, pairing code หรือ session token
47
+
24
48
  ## Setup foundation
25
49
 
26
50
  1. ใช้ config directory ชั่วคราวและรัน `dodo setup --check` กับ `dodo setup --plan`
@@ -0,0 +1,46 @@
1
+ # DODO MCP 1.0.1
2
+
3
+ Patch release นี้ปรับความน่าเชื่อถือของ platform/release gates โดยไม่เปลี่ยน runtime
4
+ หรือ API contract จาก 1.0.0 ระบบ multi-project, AI provider/profile, owner web workspace
5
+ และ Sub-agent controls ยังคงทำงานตาม contract เดิม และไม่เปลี่ยน security authority
6
+ ของ OAuth, project ACL, workspace ID/epoch, trust, approval, path/secret guard,
7
+ expected hash หรือ command sandbox
8
+
9
+ ## สิ่งที่เปลี่ยน
10
+
11
+ - เพิ่ม Linux media, tunnel credential และ gate-evidence regression tests
12
+ - ทำ source fingerprint และ persistent Windows checkout ให้ deterministic แม้ runner
13
+ เคยใช้ CRLF policy ต่างจาก repository
14
+ - ตรวจ canonical tracked bytes และ clean index ก่อนเริ่ม Windows gate
15
+ - เก็บ sanitized gate summary ใน log เมื่อ GitHub artifact quota เต็ม โดยไม่ทำให้
16
+ artifact upload กลายเป็นตัวตัดสินผล test
17
+
18
+ ## ติดตั้งและอัปเดต
19
+
20
+ ```bash
21
+ npm install -g dodo-mcp@1.0.1
22
+ dodo --version
23
+ ```
24
+
25
+ หลังอัปเดตให้ restart process ที่กำลังรันอยู่ และให้ MCP client rescan/recreate connection
26
+ หาก client เก็บ tool catalog เดิมไว้
27
+
28
+ ## Tool surface
29
+
30
+ - HTTP ใช้ Compact 19 tools เป็นค่าเริ่มต้น
31
+ - STDIO ใช้ Full 121 tools เป็นค่าเริ่มต้น
32
+ - เมื่อเจ้าของเปิด Sub-agent MCP exposure แล้ว Full เป็น 125 tools
33
+ - Hybrid คง 49 tools
34
+
35
+ การซ่อนหรือเปิด Sub-agent operations เปลี่ยนเฉพาะ catalog exposure ไม่เพิ่ม OAuth scope,
36
+ project ACL, trust, profile authority หรือ approval
37
+
38
+ ## ผลตรวจอัตโนมัติ
39
+
40
+ - macOS Node 22: core 669 ผ่าน, packaging 16/16, audit 0, benchmark 7/7 และ fresh install ผ่าน
41
+ - Windows native Node 22 และ 24: core 671 ผ่านต่อรุ่น, packaging 16/16, audit 0,
42
+ benchmark 7/7 และ fresh install ผ่าน
43
+ - Linux Docker ใช้หลักฐาน gate ที่ผ่านก่อนหน้า; release follow-up นี้ไม่ได้รัน Linux ซ้ำ
44
+
45
+ ผล live provider, external MCP client และ Windows desktop permission จริงยังเป็น
46
+ `MANUAL_NOT_RUN` จนกว่าเจ้าของจะทดสอบด้วย credential และ environment จริง
@@ -0,0 +1,46 @@
1
+ # DODO MCP 1.0.2
2
+
3
+ Patch release นี้เพิ่ม Temporary Remote Config สำหรับเจ้าของที่ต้องการเปิดหน้า Config
4
+ ผ่าน Cloudflare Tunnel ชั่วคราว โดยคง Local Config ที่พอร์ต 21731 ไว้บน loopback และ
5
+ คง MCP/OAuth ที่พอร์ต 21730 ตามเดิม
6
+
7
+ ## สิ่งที่เปลี่ยน
8
+
9
+ - เพิ่ม `dodo --web`, `dodo web --status` และ `dodo web --close`
10
+ - เปิด `/config` บน public listener ครั้งละไม่เกินหนึ่งชั่วโมงและปิดเป็น 404 เมื่อไม่มี
11
+ lease, หลังปิด หรือเมื่อหมดอายุ
12
+ - ใช้ pairing code แบบครั้งเดียวกับ session cookie ที่เป็น Secure, HttpOnly,
13
+ SameSite=Strict และจำกัด path ที่ `/config`
14
+ - ต่ออายุ Remote Config ผ่าน authenticated owner IPC ได้โดยไม่ restart MCP
15
+ - รองรับการส่ง temporary Cloudflare Tunnel token ไปยัง process ที่รันอยู่ผ่าน private
16
+ IPC โดยไม่เก็บ token ลง config, audit, URL หรือ command arguments
17
+ - เพิ่มเมนู Temporary Remote Config ใน `dodo --cli` พร้อม security, integration,
18
+ browser และ packaging coverage
19
+
20
+ ## ขอบเขตความปลอดภัย
21
+
22
+ Remote Config ไม่เพิ่ม OAuth scope, project ACL, trust หรือ approval และไม่เพิ่ม MCP
23
+ tool สำหรับเปิดหน้า owner เส้นทาง public จะ proxy ไปยัง Local Config ด้วย capability
24
+ ภายใน process หลังผ่าน pairing/session เท่านั้น พร้อมคง Host/Origin, workspace/epoch,
25
+ validation, policy และ audit ของ Local Config
26
+
27
+ ## ติดตั้งและอัปเดต
28
+
29
+ ```bash
30
+ npm install -g dodo-mcp@1.0.2
31
+ dodo --version
32
+ ```
33
+
34
+ หลังอัปเดตให้ restart DODO process ที่กำลังรันอยู่ จากนั้นใช้ `dodo --web` เมื่อ public
35
+ origin และ tunnel route พร้อมใช้งาน
36
+
37
+ ## ผลตรวจ
38
+
39
+ - `npm run test:all`: **AUTOMATED_PASS** — 677 passed, 34 skipped, 0 failed;
40
+ packaging 16/16
41
+ - Chromium desktop 1440×900 และ mobile 390×844: **AUTOMATED_PASS** ใน fixture
42
+ - `npm audit --omit=dev`: **AUTOMATED_PASS** — 0 vulnerabilities
43
+ - public Cloudflare hostname → Remote Config จากอุปกรณ์ภายนอก: **MANUAL_NOT_RUN**
44
+
45
+ ผลจาก automated fixture ไม่ยืนยันสถานะ DNS, Tunnel หรือการเชื่อมต่อจาก AI client
46
+ ของเจ้าของ
@@ -1,5 +1,47 @@
1
1
  # DODO MCP — Release Notes
2
2
 
3
+ ## 1.0.2
4
+
5
+ ### Temporary Remote Config
6
+
7
+ - เพิ่ม `dodo --web`, `dodo web --status` และ `dodo web --close` สำหรับเปิดหน้า Config
8
+ ผ่าน public Tunnel ที่พอร์ต 21730 ครั้งละไม่เกิน 1 ชั่วโมง โดยพอร์ต 21731 ยังคง bind
9
+ loopback เท่านั้น
10
+ - ใช้ pairing code แบบครั้งเดียวและ Secure/HttpOnly/SameSite cookie ที่จำกัด path
11
+ `/config`; route ปิดเป็น 404 ก่อนเปิด หลังปิด และเมื่อหมดอายุ
12
+ - process ที่เริ่ม local-only สามารถรับ run-scoped Tunnel token ผ่าน authenticated
13
+ installation IPC โดยไม่ restart MCP และไม่บันทึก credential
14
+ - เพิ่มเมนู Remote Config ใน `dodo --cli`, security/integration/Chromium tests และ
15
+ ADR-047 โดยไม่เปลี่ยน OAuth, project authority, workspace context หรือ owner policy
16
+
17
+ ติดตั้งหรืออัปเดตด้วย `npm install -g dodo-mcp@1.0.2` จากนั้น restart process ที่กำลัง
18
+ รันอยู่ การเปิด Remote Config ผ่าน public hostname จริงยังต้องตรวจด้วย tunnel/DNS ของ
19
+ เจ้าของและไม่ถือว่าผ่านจาก automated fixture เพียงอย่างเดียว
20
+
21
+ ## 1.0.1
22
+
23
+ ### Platform และ release hardening
24
+
25
+ - เพิ่ม Linux Docker gate และ Windows native self-hosted gates สำหรับ Node 22/24
26
+ - ทำ Windows checkout/fingerprint ให้ deterministic เมื่อ runner ถูกใช้ซ้ำและมีนโยบาย
27
+ CRLF ต่างจาก repository โดยตรวจ tracked tree ให้สะอาดก่อนเริ่ม gate
28
+ - release gate ตรวจ typecheck, lint, core/security/compatibility, packaging, production
29
+ dependency audit, DodoBench และ fresh exact-tarball install
30
+ - เพิ่ม regression tests สำหรับ Linux media, tunnel credential handling และการจัดการ
31
+ gate evidence โดยไม่เปลี่ยน runtime/API contract จาก 1.0.0
32
+ - ไม่มีการลด OAuth, project ACL, workspace context, approval, path/secret guard,
33
+ expected-hash conflict protection หรือ command sandbox
34
+
35
+ ## วิธีอัปเดตเป็น 1.0.1
36
+
37
+ ```bash
38
+ npm install -g dodo-mcp@1.0.1
39
+ dodo --version
40
+ ```
41
+
42
+ หลังติดตั้งให้ restart DODO process และ rescan/recreate MCP connection เมื่อ client cache
43
+ tool catalog เดิม
44
+
3
45
  ## 1.0.0
4
46
 
5
47
  ### Global launcher และ interactive CLI