feat(local-mode): local-first developer experience with zero-ceremony setup#2544
feat(local-mode): local-first developer experience with zero-ceremony setup#2544
Conversation
🧪 BenchmarkShould we run the Virtual MCP strategy benchmark for this PR? React with 👍 to run the benchmark.
Benchmark will run on the next push after you react. |
Release OptionsShould a new version be published when this PR is merged? React with an emoji to vote on the release type:
Current version: Deployment
|
There was a problem hiding this comment.
6 issues found across 15 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/api/routes/auth.ts">
<violation number="1" location="apps/mesh/src/api/routes/auth.ts:126">
P1: This unauthenticated endpoint grants full admin access with no credentials. If the server is ever bound to `0.0.0.0` (or another non-loopback address), any machine on the local network can obtain an admin session. Add a host/origin check to restrict this endpoint to loopback requests only (`127.0.0.1` / `::1` / `localhost`).</violation>
</file>
<file name="apps/mesh/scripts/dev.ts">
<violation number="1" location="apps/mesh/scripts/dev.ts:75">
P2: Tilde expansion drops the home directory for inputs like "~/dir" because `answer.slice(1)` starts with `/`. Strip the leading slash before joining so the resolved path stays under the user home.</violation>
<violation number="2" location="apps/mesh/scripts/dev.ts:119">
P2: Generate a random ENCRYPTION_KEY when none is provided instead of storing an empty string; an empty key is hashed into a deterministic value, so all installs share the same derived encryption key.</violation>
</file>
<file name="apps/mesh/src/index.ts">
<violation number="1" location="apps/mesh/src/index.ts:74">
P2: Local-mode seeding runs as a detached async task after the server starts, so `/api/auth/custom/local-session` can be called before the admin user is created and returns a 500. Gate local-session until seeding finishes or wait for the seed before exposing auto-login.</violation>
</file>
<file name="apps/mesh/src/cli.ts">
<violation number="1" location="apps/mesh/src/cli.ts:184">
P1: DATABASE_URL is unconditionally overwritten, silently ignoring user-provided values (e.g., PostgreSQL URLs). The help text documents it as a configurable env var with a default, but the code always replaces it. This breaks the PostgreSQL deployment path described in the project README.
Respect the user's DATABASE_URL when explicitly set, and only default to the SQLite path inside MESH_HOME.</violation>
</file>
<file name="apps/mesh/src/auth/local-mode.ts">
<violation number="1" location="apps/mesh/src/auth/local-mode.ts:14">
P1: Hardcoding a known admin password makes the seeded account trivially guessable. Use a generated or environment-provided secret so local-mode credentials aren’t predictable if this mode is accidentally enabled outside a dev machine.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
2e2e14a to
993a4fe
Compare
|
@cubic-dev-ai can you re-review? |
@vibegui I have started the AI code review. It will take a few minutes to complete. |
There was a problem hiding this comment.
6 issues found across 15 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/auth/local-mode.ts">
<violation number="1" location="apps/mesh/src/auth/local-mode.ts:86">
P2: Organization slug is derived from raw OS username without normalization, so invalid slug characters can be persisted.</violation>
</file>
<file name="apps/mesh/src/cli.ts">
<violation number="1" location="apps/mesh/src/cli.ts:160">
P1: Missing non-TTY guard: if the CLI is run non-interactively (Docker, CI, systemd) without `--home` and without an existing `~/deco` directory, the `prompt()` call will hang forever. `scripts/dev.ts` correctly checks `process.stdin.isTTY` and falls back to a default, but `cli.ts` does not.</violation>
</file>
<file name="apps/mesh/scripts/dev.ts">
<violation number="1" location="apps/mesh/scripts/dev.ts:41">
P3: This file duplicates substantial startup/secrets logic from `src/cli.ts`; extract a shared module to prevent divergence and double-maintenance.</violation>
<violation number="2" location="apps/mesh/scripts/dev.ts:119">
P1: `ENCRYPTION_KEY` is initialized to an empty string, which results in a predictable global encryption key instead of a unique secret.</violation>
<violation number="3" location="apps/mesh/scripts/dev.ts:127">
P2: `secrets.json` is written without explicitly restricting file permissions, which can expose auth/encryption secrets on permissive environments.</violation>
</file>
<file name="apps/mesh/src/web/routes/login.tsx">
<violation number="1" location="apps/mesh/src/web/routes/login.tsx:75">
P2: Auto-login should retry transient local-session failures instead of failing permanently on the first 5xx response, otherwise users can get stuck on an error screen during initial startup/seeding.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
1 issue found across 6 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/web/components/chat/task/use-task-manager.ts">
<violation number="1" location="apps/mesh/src/web/components/chat/task/use-task-manager.ts:152">
P2: This initializer resets the active task on every hook initialization/remount, causing unstable chat selection and unnecessary localStorage churn. Preserve an existing value in the initializer so only first-time initialization generates a UUID.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
422e1d8 to
01e38af
Compare
There was a problem hiding this comment.
2 issues found across 13 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/auth/index.ts">
<violation number="1" location="apps/mesh/src/auth/index.ts:328">
P1: The localhost trusted origin is added in all environments (and forced to `http`), which unintentionally broadens `trustedOrigins` outside development.</violation>
</file>
<file name="packages/studio/scripts/publish.sh">
<violation number="1" location="packages/studio/scripts/publish.sh:63">
P2: `package.json` restoration is not guaranteed on failure; the script can exit early and leave the file patched.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
3 issues found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/scripts/dev.ts">
<violation number="1" location="apps/mesh/scripts/dev.ts:75">
P2: Tilde expansion trims one extra character for `~<name>` inputs because `slice(2)` is used with a broad `startsWith("~")` check.</violation>
</file>
<file name="apps/mesh/src/cli.ts">
<violation number="1" location="apps/mesh/src/cli.ts:177">
P2: Interactive `~` path expansion removes one extra character (`slice(2)`), producing incorrect directories for inputs like `~mydir` and diverging from `--home` behavior.</violation>
</file>
<file name="apps/mesh/src/api/routes/auth.ts">
<violation number="1" location="apps/mesh/src/api/routes/auth.ts:132">
P0: Loopback check relies on spoofable client headers (`x-forwarded-for`, `x-real-ip`), making it trivially bypassable. Any LAN client can set `X-Forwarded-For: 127.0.0.1` to gain admin access. Additionally, the `remoteAddr === ""` fallback means any direct connection (no proxy) passes the check regardless of origin.
Use `getConnInfo` from `hono/bun` to read the actual socket remote address instead of trusting client-supplied headers.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
8 issues found across 14 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/docs/client/src/content/draft/pt-br/introduction.mdx">
<violation number="1" location="apps/docs/client/src/content/draft/pt-br/introduction.mdx:31">
P3: The quickstart command now conflicts with its own heading (`npx` vs `bunx`), which can confuse users following the setup instructions.</violation>
</file>
<file name="apps/mesh/src/cli.ts">
<violation number="1" location="apps/mesh/src/cli.ts:64">
P2: CLI help text now points users to `bunx decocms`, which conflicts with the documented `npx decocms` entrypoint and can misdirect users without Bun installed.</violation>
</file>
<file name="apps/docs/client/src/content/latest/en/introduction.mdx">
<violation number="1" location="apps/docs/client/src/content/latest/en/introduction.mdx:32">
P2: The quickstart command now contradicts the `(npx)` option label and documented onboarding path; this may block users without Bun installed. Keep this command as `npx decocms` (or rename the option to Bun explicitly).</violation>
</file>
<file name="apps/docs/client/src/content/latest/pt-br/mcp-mesh/quickstart.mdx">
<violation number="1" location="apps/docs/client/src/content/latest/pt-br/mcp-mesh/quickstart.mdx:16">
P3: The quickstart instruction is inconsistent: the section says "(npx)" but the command uses `bunx`, which can confuse users about the expected tool.</violation>
</file>
<file name="apps/docs/client/src/content/latest/pt-br/introduction.mdx">
<violation number="1" location="apps/docs/client/src/content/latest/pt-br/introduction.mdx:32">
P2: The quick-start command was changed to `bunx decocms`, which conflicts with the documented `npx` flow and adds an unintended Bun prerequisite for users.</violation>
</file>
<file name="apps/docs/client/src/content/latest/en/mcp-mesh/quickstart.mdx">
<violation number="1" location="apps/docs/client/src/content/latest/en/mcp-mesh/quickstart.mdx:16">
P3: The quickstart command conflicts with the surrounding "(npx)" instructions; use `npx decocms` here to keep the documented setup path consistent.</violation>
</file>
<file name=".github/workflows/release.yaml">
<violation number="1" location=".github/workflows/release.yaml:154">
P2: The release output’s “Install via npm” command was changed to `bunx`, which requires Bun and conflicts with npm-targeted install instructions. Keep npm instructions on `npx` to avoid breaking copy/paste setup for npm users.</violation>
</file>
<file name="apps/docs/client/src/content/draft/pt-br/mcp-mesh/quickstart.mdx">
<violation number="1" location="apps/docs/client/src/content/draft/pt-br/mcp-mesh/quickstart.mdx:16">
P3: The command now contradicts the section label `(npx)`, which can mislead users about which runner to use. Keep this line consistent with the documented option.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
@cubic-dev-ai anything else? |
@vibegui The core logic for the zero-ceremony setup is robust, especially the security boundary around the local session endpoint. I have a few minor observations for
The move to track the NATS and OpenRouter onboarding in |
… setup Running `bunx @decocms/mesh` (or `bun run dev`) now works with no config: - Prompts for a data directory on first run (defaults to ~/deco/) - Auto-generates and persists BETTER_AUTH_SECRET + ENCRYPTION_KEY - Seeds an admin user from the OS username (e.g. viktor@localhost.mesh) and a default "Viktor Local" org on fresh databases - Auto-logs in the browser without a login form (MESH_LOCAL_MODE=true) - Skips org selection and redirects straight to the first org - Enables local filesystem object storage (/mcp/dev-assets) for assets - Adds --home and --no-local-mode CLI flags; renames old dev to dev:saas New files: - apps/mesh/scripts/dev.ts — mirrors CLI setup for `bun run dev` - apps/mesh/src/auth/local-mode.ts — seedLocalMode, getLocalAdminUser - POST /api/auth/custom/local-session — auto-sign-in endpoint (local only) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
… fixes - Generate random ENCRYPTION_KEY instead of empty string (cli.ts, dev.ts) - Restrict secrets.json file permissions to 0o600 (cli.ts, dev.ts) - Add non-TTY guard to prevent prompt() hang in Docker/CI (cli.ts) - Normalize org slug from OS username to valid [a-z0-9-] (local-mode.ts) - Retry auto-login on 5xx with exponential backoff (login.tsx) - Always start with fresh chat on page load (use-task-manager.ts) - Autofocus chat input on load (tiptap/input.tsx) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use module-level UUID constant so the fresh-chat behavior is stable within a session while still generating a new chat on each page load. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix TS2345 errors: add non-null assertions for Hono route params (threadId, toolName) that are always present in route patterns - Respect MESH_LOCAL_MODE env var in dev.ts instead of hardcoding true - Set MESH_LOCAL_MODE=false in e2e workflow so signup form is shown Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add localhost:PORT to Better Auth trusted origins for proxy hostnames - Add getInternalUrl() for server-to-server connections (mcp/self, dev-assets) - Use BASE_URL in startup logs and CLI banner - Simplify no-LLM empty state (remove tasks panel when no model connected) - Change HTML title to "Deco Studio" Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Transitional package so `bunx @decocms/studio` works while the canonical package remains @decocms/mesh. Includes local publish script and CI workflow that auto-publishes with the same version as mesh. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
External OAuth servers (e.g. OpenRouter MCP) reject .localhost subdomains as redirect URIs. When running behind a proxy (tokyo.localhost → localhost:3000), the OAuth flow now uses localhost:PORT for the redirect URI. - Add setOAuthRedirectOrigin() to mesh-sdk for configuring redirect origin - Expose internalUrl in /api/config public endpoint - Set redirect origin from config on app init in ThemeProvider - Accept cross-origin postMessage between .localhost variants in local dev Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…n on failure - Only add localhost:PORT to Better Auth trusted origins in local mode - Add EXIT trap to studio publish script to restore package.json on failure Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Restrict /local-session endpoint to loopback requests only (127.0.0.1/::1) to prevent LAN access when server is bound to 0.0.0.0 - Fix tilde expansion in dev.ts and cli.ts: ~/dir was resolving to /dir instead of $HOME/dir (slice(1) → slice(2) to skip the ~/) - Gate local-session behind seed completion to prevent 500s on race - Respect user-provided DATABASE_URL in CLI instead of always overwriting - Add security comment documenting that loopback check (not password) is the security boundary for local-mode auto-login Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update test mocks to include hostname/port/protocol on window.location
and adjust assertion for postMessage target origin ("*" in local dev).
Also add DEBT.md tracking technical debt for local-first DX work.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace spoofable X-Forwarded-For header check with Bun's getConnInfo() (socket-level requestIP) for the local-session loopback guard - Pass Bun server as env to Hono so getConnInfo can access requestIP - Fix tilde expansion: only expand bare ~ and ~/path, not ~user inputs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Publish as `decocms` on npm so users can run `npx decocms` or `npm i -g decocms && deco`. Renamed bin from studio.js to deco.js and updated CI workflow, publish script, README, and DEBT.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace internal wrapper docs with a proper product README showing features, quick start, and MCP client config. Add descriptive keywords and update main README to reference `npx decocms`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Production deployments don't need the localhost URL in /api/config. Gate it behind isLocalMode() so it's only sent when running locally behind a proxy (e.g. tokyo.localhost). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Rewrite to lead with agents and tools, not just MCP plumbing. Matches the landing page: hire agents, connect 50+ integrations, track costs, run locally or scale to teams. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@decocms/mesh is resolved via require() in bin/deco.js at runtime, which knip can't trace. Add ignoreDependencies for packages/studio. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update all user-facing references: CLI help text, release workflow, docs quickstart pages (en + pt-br), dev script comment, and OAuth client name to use "Deco Studio" / "npx decocms" / "deco". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The built CLI uses Bun APIs (Bun.file, Bun.serve), so the wrapper must run it with bun, not node. Auto-detects bun availability. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update startup banners, prompts, and subtitle in cli.ts and dev.ts to show "Deco Studio" instead of "MCP Mesh". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The built CLI uses Bun APIs so bun is a hard requirement. Show a clear error with install instructions if bun is missing. Update all docs, CLI help, README, and release workflow from npx to bunx. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update "(npx)" headings to "(bunx)" in quickstart docs and change "Install via npm" to "Install via Bun" in release workflow to match the actual bunx decocms commands. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use nullish coalescing for DATABASE_URL to not override user-provided values (e.g., PostgreSQL connection strings), matching cli.ts behavior - Replace brittle string replace with join(import.meta.dir, "..") for cross-platform path handling Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
15fe181 to
89fb8ed
Compare
… prod guard - Extract shared bootstrap module (scripts/bootstrap.ts) from cli.ts and dev.ts - Generate per-install LOCAL_ADMIN_PASSWORD in secrets.json instead of hardcoded value - Fix seed gate: resolve immediately when not in local mode (prevents hang) - Add .catch() to dynamic import chain in index.ts - Tighten postMessage target from "*" to getOAuthRedirectOrigin() - Refuse local mode in production (NODE_ENV=production) unless explicitly overridden Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
4 issues found across 7 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/index.ts">
<violation number="1" location="apps/mesh/src/index.ts:38">
P1: Use an explicit boolean-string check for `MESH_ALLOW_LOCAL_PROD`; the current truthiness check can unintentionally allow local mode in production.</violation>
<violation number="2" location="apps/mesh/src/index.ts:103">
P2: Handle local-mode module import failure as fatal (or otherwise resolve the seed gate); currently failures can leave `waitForSeed()` unresolved and break local-session auth.</violation>
</file>
<file name="apps/mesh/scripts/dev.ts">
<violation number="1" location="apps/mesh/scripts/dev.ts:36">
P1: Non-interactive runs can incorrectly use `~/deco` instead of the CI-safe `.mesh-dev` path.</violation>
</file>
<file name="packages/mesh-sdk/src/lib/mcp-oauth.ts">
<violation number="1" location="packages/mesh-sdk/src/lib/mcp-oauth.ts:560">
P2: Using getOAuthRedirectOrigin() as the postMessage target in local dev can break OAuth callbacks behind proxies, because the opener origin (e.g. proxy.localhost) won’t match the redirect origin (localhost). The message is dropped when targetOrigin mismatches, so the parent window never receives the OAuth code. Keep the permissive target for local dev or otherwise ensure the target matches the opener’s origin.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
…cking - Remove hardcoded "admin@mesh" fallback password — throw on missing secrets.json - Fix seed gate hang when local-mode module import fails (markSeedComplete in catch) - Fix directory traversal in sanitizeKey() — use path.resolve() + containment check - Unify DEV_ASSETS_BASE_DIR to use MESH_HOME in both dev-assets files - Use crypto.timingSafeEqual() for HMAC signature verification - Remove production console.log leaking taskId on every stream resume - Cherry-pick OAuth flow extraction from feat/oauth-flow-extraction - Update DEBT.md with deferred review issues from merged PRs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…proxy setups - Use `!== "true"` instead of truthiness check for MESH_ALLOW_LOCAL_PROD to prevent unintended bypass with values like "false" - Use "*" as postMessage target in local dev so OAuth callbacks work behind proxies where opener origin differs from redirect origin Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
getLocalAdminPassword() now checks the default ~/deco directory as a fallback when MESH_HOME is not set. This fixes auto-login when running via `bun run dev:server` directly (which doesn't set MESH_HOME), while still refusing to use a hardcoded credential. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…veMeshHome Swap the order so CI/non-TTY environments always use the CI-safe .mesh-dev path, even when ~/deco exists from a prior local run. Previously, existsSync(~/deco) was checked first, causing CI on shared runners to use the developer's real data directory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The LLM was self-identifying as "decoCopilot" which reads as "decoco" in Portuguese. Use properly spaced "Deco Pilot" and "Deco Studio". Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Zero-config local-first developer experience for MCP Mesh. Run
npx decocmsand get a fully functional MCP gateway on your machine in seconds.Local mode (
MESH_LOCAL_MODE=true)~/deco/)BETTER_AUTH_SECRET+ENCRYPTION_KEYinsecrets.json(chmod 0600)/mcp/dev-assets) in local mode--home <path>,--no-local-modedecocmsnpm packagenpx decocms/npm i -g decocms && deco— user-facing CLIpackages/studio/that depends on@decocms/meshOAuth behind proxies
/api/configexposesinternalUrlonly in local mode for OAuth redirect URIssetOAuthRedirectOrigin()in mesh-sdk lets the client uselocalhost:PORTfor redirectspostMessagesupport between.localhostvariants (e.g.tokyo.localhost↔localhost:3000)localhost:PORTorigin only whenisLocalMode()is trueSecurity hardening
/local-sessionrestricted to loopback IPs viagetConnInfofromhono/bun(socket-level, non-spoofable)serverpassed as Hono env inBun.serve()forgetConnInfoto workmarkSeedComplete/waitForSeed) prevents race between seeding and auto-loginDATABASE_URLrespects user-provided value (no silent overwrite)~and~/path, not~userENCRYPTION_KEYgenerated if not providedOther
bun run devrenamed tobun run dev:saas; newbun run devrunsscripts/dev.tsinternalUrlremoved from production/api/configresponsesDEBT.mdTest plan
npx decocms→ prompted for data dir → browser opens, lands in org without login--no-local-mode: login form shown as normal--home ~/custom-dir: data stored there/local-sessionreturns 403 from non-loopback IP/local-sessionreturns 503 before seed completes, then succeedstokyo.localhost)/api/configomitsinternalUrlwhenMESH_LOCAL_MODEis not setbun testpasses (except pre-existing Playwright/bun conflict)🤖 Generated with Claude Code