Skip to content

Codebase↔strategy audit: simplify the MCP-mesh machinery, converge deploy previews with sandboxes-as-production (agent-ready work items) #4468

Description

@vibegui

How to use this issue. Each work item below is written to be handed to a coding agent almost verbatim: it states the goal, the evidence (with file:line), the concrete scope, acceptance criteria, and strategic risk. Copy a ### WI-N block into your agent's prompt, add "open a PR against main", and go. Items are ordered by leverage. Verify the ⚠️ items with the owner before deleting anything.

Where this came from

A structural audit of the monorepo built from a graphify knowledge graph (2,764 files → 16,839 nodes / 44,925 edges, deterministic tree-sitter AST), cross-referenced against the company 2028 plan (FUTURE.md / MILESTONES.md M1–M4). Companion doc: GRAPHIFY_FINDINGS.md (per-file bug findings). This issue is the strategy-level layer: what to simplify, delete, and re-architect so the code maps to where revenue actually is.

The reframe (root cause of most items)

The code describes itself as "an open-source control plane for MCP traffic." By mass and by strategy that's a stale label:

  • 56% of the app is frontend (apps/mesh/src/web = 152k LOC). The three heaviest UI areas — chat (25k), sandbox/preview (19k), sections-editor (18k) — are the agentic operator console (M2) and the agentic site editor (M3). That is the product.
  • The MCP plumbing (connections, registry, virtual-MCPs, bindings, event-bus) is real, but it's substrate for the agentic editor + operator — not a standalone "MCP mesh" offering.

Everything built to satisfy "we are a generic MCP mesh" is a cut candidate. Everything built for "agentic commerce editor + operator" is an invest candidate. Guiding principle: the worst thing is to optimize what shouldn't exist.

Measured code mass (LOC, ts/tsx, excl. tests):

Area LOC Maps to
web/components/chat 24.9k M2 operator console (keep)
web/components/sandbox 19k M3 editor / M4 preview substrate (keep)
web/components/sections-editor 18.2k M3 agentic editor (keep)
api/ 25.8k mixed
tools/ 21.2k mixed — registry(32), virtual(22) over-built
storage/ 15.3k mixed
packages/sandbox 19k M4 deploy substrate (keep + extend)

Group A — Docs debt (do first, cheapest, highest-leverage)

WI-1 — Rewrite the CLAUDE.md "Event Bus" section to match reality

Label: documentation · Risk: none

Problem. CLAUDE.md's largest architecture section documents an Event Bus (CloudEvents v1.0, 20-attempt backoff, deliverAt, cron, 6 MCP tools EVENT_PUBLISH/EVENT_SUBSCRIBE/…, a worker, polling.ts, nats-notify.ts) that does not exist in code. Any coding agent reading it will build on a phantom.

Evidence (verified):

  • apps/mesh/src/tools/eventbus/directory does not exist (CLAUDE.md claims 6 tools live here).
  • packages/bindings/src/well-known/event-bus.tsdoes not exist (only event-subscriber.ts is present).
  • grep -r "EVENT_PUBLISH\|EVENT_SUBSCRIBE" apps packages0 matches.
  • What does exist: apps/mesh/migrations/008-event-bus.ts (schema), packages/runtime/src/events.ts (~599 LOC engine). Producers: exactly 1 (apps/mesh/src/api/routes/registry/public-publish-request.ts:103 inserts event_deliveries). Subscribers: 0 (nothing inserts event_subscriptions). Consumers/workers: 0.

Scope. Edit CLAUDE.md: replace the entire Event Bus section (files, tools, bindings, ON_EVENTS, NotifyStrategy, config) with an accurate short note — "an events schema + engine exist (runtime/src/events.ts, migration 008) with one producer and no consumers; the working trigger path is AutomationEventDispatcher + DBOS (see WI-2/WI-7)." Do not delete code in this WI — that's WI-6.

Acceptance: CLAUDE.md no longer references any file/symbol that grep can't find; a fresh reader can trust every path in the Event Bus section.


Group B — Delete / collapse over-built control-plane machinery

Removing these deletes ~3–4k LOC and three "we're a generic MCP mesh" fictions that keep pulling design attention off the commerce product.

WI-6 — Delete (or consciously defer) the Event Bus engine

Label: refactor · Risk: LOW ⚠️ (confirm with owner: aspirational-but-unbuilt vs planned-next)

Evidence: see WI-1. 1 producer, 0 subscribers, 0 consumers, 0 workers. The real trigger feature bypasses it (AutomationEventDispatcher + DBOS idempotency keys).

Scope. Remove packages/runtime/src/events.ts event-bus engine paths, the events / event_subscriptions / event_deliveries schema (new down-migration; keep 008 in history), well-known/event-subscriber.ts binding, and the storage types. Redirect the single producer (public-publish-request.ts:103) to a direct call or drop it if the publish endpoint is itself unused (check first).

Acceptance: app builds and boots; bun run check + bun test pass; no event_subscriptions/event_deliveries references remain; a migration cleanly drops the tables.

WI-2 — Collapse the Virtual-MCP "3 strategies" abstraction

Label: refactor · Risk: LOW

Problem. Docs/README describe three runtime strategies (full-context / smart-selection / code-execution). Only passthrough ships.

Evidence: apps/mesh/src/mcp-clients/virtual-mcp/passthrough-client.ts is the only impl; the strategy field is a vestigial single-member union — virtual-mcp/index.ts:71 _strategy: "passthrough" with the comment "Kept for backward compatibility, always uses passthrough." VirtualMCPEntitySchema has no strategy field. smart-selection/code-exec exist only on worktree branch happy-nibbling-matsumoto (where migration 012 self-labels them "metadata for now").

Scope. Remove the _strategy union and any dead branching; update apps/docs/ + CLAUDE.md to stop claiming three strategies until the worktree lands. Leave one clean extension seam if desired, but no dead code.

Acceptance: no _strategy union in shipped code; docs describe only what ships; typecheck + tests pass.

WI-4 — Prune unused well-known bindings

Label: refactor · Risk: LOW

Evidence (packages/bindings/src/well-known/): prompt.ts (PROMPTS_BINDING — 0 uses), registry.ts (REGISTRY_APP_BINDING — 0 external refs), event-subscriber.ts (removed by WI-6), and the client halves of assistant.ts / ai-gateway.ts / brand.ts (unused). Keep the 4 real ones: collections.ts (18 importers — backbone of the list/get tool surface), object-storage.ts (14 refs), trigger.ts (used in configure-trigger.ts:35); language-model.ts is @deprecated (see connection/list.ts:67) — mark for removal, don't break callers yet.

Scope. Delete the unused binding constants + their exports; leave collections/object-storage/trigger untouched.

Acceptance: bun run check passes; knip reports no new unused exports; grep confirms 0 references to the removed constants.

WI-6b — Derive registry-metadata.ts from tool definitions

Label: refactor · Risk: LOW

Evidence: apps/mesh/src/tools/registry/registry-metadata.ts — ~41KB, ~142 hand-maintained {name, description, category} entries with a manual "keep in sync with index.ts" warning (chronic drift hazard; flagged as a god-file by the graph).

Scope. Generate the metadata from the tool defineTool() definitions (name/description already live there; add category to the tool def if missing) instead of a parallel hand-maintained table.

Acceptance: registry-metadata.ts no longer hand-lists tools; the registry UI shows identical categories/descriptions; adding a tool requires no metadata edit.


Group C — Heavier simplifications (verify usage first)

WI-3 — Shrink or externalize the Registry MCP-monitor subsystem

Label: refactor · Risk: LOW-MED ⚠️ (confirm no customer depends on health scores)

Evidence: apps/mesh/src/tools/registry/monitor-run-start.ts = 1,262 LOC / 38KB — the graph's #15 god-file. It's a full LLM agent that connects to third-party MCPs and exercises their tools (full_agent vs health_check modes), backed by 8 monitor tools + storage — ~35% of the 32-tool registry domain, for a catalog QA feature, not the revenue path.

Scope (pick one): (a) reduce to a plain listTools reachability check, or (b) extract the LLM-driven tester into a separate package/service. Also: fold the duplicate COLLECTION_REGISTRY_APP_* read tools into REGISTRY_ITEM_* with a privateOnly flag (collection-registry-app.ts:14-30 mirror REGISTRY_ITEM_* with one filter), and drop REGISTRY_AI_GENERATE if the UI doesn't call it (check web/ first).

Acceptance: registry catalog still lists/reaches MCPs; god-file gone or moved out of apps/mesh; tool count in the domain drops; no frontend caller breaks.

WI-5 — Reconsider the OpenAI-compatible gateway endpoint

Label: refactor · Risk: MED ⚠️ (verify external API-key consumers before removing)

Evidence: apps/mesh/src/api/routes/openai-compat.ts = 773 LOC — a generic OpenAI-compatible gateway. Pure control-plane positioning; no tie to commerce/editor revenue.

Scope. Establish whether any external consumer hits this (logs/analytics). If not, externalize to the Deco AI Gateway service and remove from mesh. If yes, document the dependency and leave it.

Acceptance: a decision recorded (remove / externalize / keep-with-reason) backed by usage data.


Group D — Deployment: previews and prod are the SAME investment

Strategic anchor (M4, MILESTONES.md): "One platform — GCP cluster shut down, sandboxes-as-production, MCP-native." Today: Argo CD GitOps (decocms/argo-deploy-mesh, manual update-image-tag.yml), GCP k8s, staging→prod promoted by hand, no deploy previews for Studio itself.

Key finding: you already built ~80% of a preview system. A sandbox is a per-(user, branch) K8s SandboxClaim that shallow-clones a repo → installs deps → runs dev/start → gets a stable URL <branch-slug>-<hash>.preview.<domain> (per-claim Gateway HTTPRoute + wildcard TLS) → auto-GCs on 15-min idle. SANDBOX_START already does this for arbitrary GitHub repos. Branch → running app → stable URL → auto-teardown already works. The gaps for "Studio-of-a-branch preview" are the same gaps for "sandboxes-as-production."

Reference files: packages/sandbox/server/provider/agent-sandbox/runner.ts, packages/sandbox/daemon/setup/orchestrator.ts, packages/sandbox/daemon/proxy.ts, packages/sandbox/shared.ts, apps/mesh/src/sandbox/lifecycle.ts, apps/mesh/src/sandbox/preview-proxy.ts, apps/mesh/src/api/routes/sandbox-proxy.ts, apps/mesh/src/tools/sandbox/start.ts, deploy/helm/sandbox-env/ (template, warm-pool, preview-gateway, housekeeper), .github/workflows/release-mesh.yaml, release-studio-sandbox.yaml.

WI-7 — Add a build step to the sandbox daemon setup pipeline

Label: enhancement · Risk: LOW · Prereq for WI-9/WI-10

Evidence: packages/sandbox/daemon/setup/orchestrator.ts runs a FIFO pipeline clone → install → start; start-commands are hard-coded to WELL_KNOWN_STARTERS = ["dev","start"] in packages/sandbox/shared.ts:18. There is no build step — apps run in Vite dev mode only.

Scope. Extend the Step pipeline to clone → install → build → start and broaden start-command resolution (support a built/production-serve command). Reuse everything else (clone, golden cache, probe, :9000 reverse proxy, HTTPRoute).

Acceptance: a repo with a build script produces a built app served behind the existing preview URL; existing dev/start repos still work unchanged.

WI-8 — Turn ON the features that already ship OFF

Label: enhancement · Risk: LOW · (config + infra, ~no app code)

Evidence: three accelerators/GC mechanisms exist but default off: warm pool (SandboxWarmPool CRD, deploy/helm/sandbox-env/templates/sandbox-warm-pool.yaml, warmPool.enabled:false); golden cache (GOLDEN_CACHE_ENABLED, ~1s vs the 180s readiness timeout); preview Gateway + wildcard cert + housekeeper (sandbox-preview-gateway.yaml, sandbox-preview-cert.yaml, files/housekeeper-sweep.sh).

Scope. Flip the Helm flags; provision the manual infra they need (wildcard DNS *.<domain>, a DNS-01 ClusterIssuer).

Acceptance: cold sandbox boot drops to low-single-digit seconds; previews are reachable over TLS at <handle>.preview.<domain>; idle sandboxes + orphan HTTPRoutes auto-clean.

WI-9 — Ephemeral Postgres + secret injection per sandbox

Label: enhancement · Risk: MED · (biggest net-new; shared prereq for previews AND prod)

Evidence: a sandbox provisions no database — it only clones+runs a repo. Studio needs Postgres + NATS + Better Auth + secrets. Injection channel exists (EnsureOptions.env, frozen plaintext — runner.ts:1075-1098); egress is locked down by a netinit iptables init-container (allows only 53/443 out; RFC1918 blocked — sandbox-template.yaml:125-202).

Scope. In the ensure/SANDBOX_START path: provision a branch/template-cloned ephemeral Postgres, run migrate + better-auth:migrate + seed, inject DATABASE_URL + BETTER_AUTH_SECRET + ENCRYPTION_KEY + OAuth keys via EnsureOptions.env, and add the DB host to the netinit egress allowlist.

Acceptance: a sandbox can boot a full Studio instance with an isolated DB and working auth; the DB is torn down with the sandbox.

WI-10 — Studio PR-preview job (the visible win)

Label: enhancement · Risk: LOW · Depends on WI-7 + WI-9 (+WI-8 flags)

Evidence: CI already builds the Studio image and dispatches to deco-apps-cd (release-mesh.yaml:564-571, repository_dispatch: studio-image-bump).

Scope. Add a PR workflow that calls ensure/SANDBOX_START with the Studio repo + PR branch + build-mode (WI-7) + ephemeral DB (WI-9), then comments <handle>.preview.<domain> on the PR and tears down on close.

Acceptance: every PR gets a working, isolated Studio preview URL posted as a comment; the sandbox GCs when the PR closes.

WI-11 — Durable/pinned claim mode + decouple app liveness from the daemon

Label: enhancement · high priority · Risk: MED · (the real gate for sandboxes-as-production)

Evidence: ephemerality is a design invariant — 15-min idle-TTL deletes claim+pod (runner.ts:148), RO rootfs, state is only git+org-fs. And app liveness is coupled to the daemon's single-event-loop /health: one missed poll → the whole sandbox is torn down (CLAUDE.md rule #8; see the daemon-blocking bugs in GRAPHIFY_FINDINGS.md #1/#2). A CPU-bound production request would kill the "server."

Scope. Add a durable claim mode (no shutdownTime / long TTL) and run the app as a daemon-supervised task with a forgiving health model, so a busy app process can't trip one-miss-dead teardown. Reuse the claim/adopt/resurrect machinery.

Acceptance: a pinned sandbox survives idle + a sustained CPU-bound request without teardown; recovery still works for genuinely dead pods.


Group E — Strategy gaps (not cuts — where the code is thin vs the plan)

Not immediately actionable as refactors, but the cuts above exist to free attention for these. Flagged so they're on the radar.

  • M1 diagnostic front-door lives outside this repo — engine is an external CF Worker (COMMERCE_DISCOVERY_MCP_URL = "https://commerce-skills.deco-cx.workers.dev/api/v2/mcp", packages/mesh-sdk/src/lib/constants.ts:38). This repo has only glue (web/routes/commerce-onboarding/, tools/commerce-discovery/). And it is not the default onboarding/commerce-onboarding (web/index.tsx:187) has zero internal navigation, entered only via marketing links with ?siteUrl; the app lands on org-home (web/index.tsx:111). Strategy M1 says "diagnostic is Studio's default onboarding." Self-service Stripe billing is not in this repo (no Stripe SDK).
  • M2 operator pillars are largely unbuilt — no per-org long-term memory ("mothership"), no briefs, no inbox operator mode (sidebar/footer/inbox.tsx is just invites), no real task management (task-groups = thread grouping). "System Health" exists only as a raw MCP call-log viewer (monitoring/), not the client-site-health product M2 describes.

Group F — Frontend ↔ API boundary

Should we separate the frontend from the API? Analyzed: no full split — it's already separate where it counts, and the remaining coupling is mostly a feature. Build (build:clientdist/client vs build:server), image (nginx -web container vs Bun -api container with independent tags — deploy/helm/studio/templates/deployment.yaml:66-110), and runtime (SPA → HTTP → API via @decocms/mesh-sdk, 207 files) are already separated. Only the source tree is shared, and web/ → backend imports are almost all import type (erased at build; end-to-end type safety is worth keeping). A full apps/web package split would break ~240 type imports, duplicate vite/tailwind/react-compiler/tsconfig config, and add a versioned contract — high cost, low runtime gain. The real risk is the handful of value imports that leak server code into the browser bundle. Fix that with a lint boundary, not a split.

WI-12 — Enforce the web ↔ server import boundary (lint rule) ✅ shipped as PR #4469

Label: refactor · Risk: LOW · Status: rule landed as warn; cleanup remaining

Done (PR #4469): new oxlint plugin plugins/ban-web-server-imports.js — files under apps/mesh/src/web/ may not value-import (or re-export / dynamic-import) from a @/<server-tree>/… specifier (storage, core, api, tools, auth, ai-providers, services, mcp-clients, event-bus, harnesses, sandbox, …). Allows import type, frontend-safe trees (@/mcp-apps, @/web, @/lib, @/shared), and workspace packages. 9 unit tests. Landed as warn.

Evidence — 21 pre-existing violations across 20 files to drain (the actual work item for an agent):

Server tree # Fix
@/tools/*/schema, registry/shared, registry-metadata 10 move shared Zod schemas/constants to @/shared or mesh-sdk, or import type-only. e.g. web/components/details/connection/connection-sidebar.tsx:2@/tools/connection/schema; web/components/connections/create-connection-dialog.tsx:54; web/views/virtual-mcp/types.ts:5; web/views/registry/*@/tools/registry/shared (PLUGIN_ID, MONITOR_AGENT_DEFAULT_SYSTEM_PROMPT); web/views/settings/org-role-detail.tsx:6@/tools/registry-metadata
@/ai-providers (model lists, tiers) 3 move CLAUDE_CODE_MODELS/CODEX_MODELS/resolveAgentTier data to a shared module. web/components/chat/select-model/agent-models.tsx:5-6, web/components/chat/use-agent-mode.ts:2
@/core (constants, org-archived) 3 web/utils/constants.ts:2 (MCP_MESH_KEY), web/layouts/shell-layout.tsx:39 + web/lib/auth-client.ts:9 (isOrgArchived) → move to @/shared
@/api/routes/decopilot/mesh-storage-uri 3 parseMeshStorageKey → shared util. web/components/chat/message/parts/tool-call-part/{generate-image,take-screenshot,web-search}.tsx
@/file-storage, @/commerce-discovery 2 homeDisplayName, normalizeCommerceSiteUrl → shared

Acceptance: all 21 violations resolved (each value import either moved to a frontend-safe/shared location or converted to import type); flip .oxlintrc.json rule from warnerror; bun run lint + bun run check + client build pass.

(Full split into apps/web is explicitly NOT recommended now. Revisit only if: tsc on the monolith becomes an unfixable CI bottleneck, the API is open-sourced separately from the UI, a second frontend appears, or web/API get separate teams with divergent cadences.)


Suggested order

  1. WI-1 (docs — today, unblocks correct agent work).
  2. WI-6, WI-2, WI-4, WI-6b (one cleanup PR — low risk, removes the mesh fictions).
  3. WI-7 → WI-9 → WI-8 → WI-10 (deploy previews; also advances M4 sandboxes-as-production).
  4. WI-3, WI-5 (verify usage, then trim).
  5. WI-11 (the prod-substrate gate) and Group E (invest where the strategy is thin).
  6. WI-12 cleanup (drain the 21 boundary violations, then flip the rule to error) — rule already shipped (chore(lint): enforce web ↔ server import boundary (ban-web-server-imports) #4469).

Generated with Claude Code from a graphify knowledge-graph audit. Every file:line above was cited by an agent that read the code; the Event Bus and diagnostic-location findings were independently grep-verified. ⚠️ items contradict what CLAUDE.md implies — confirm intent with the owner before deleting.

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationenhancementNew feature or requestrefactor

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions