Skip to Content
ArchitectureOverview

Architecture Overview

The core principle

Business logic lives once. Every interface — admin UI, REST endpoint, MCP tool, CLI command, webhook — is a thin adapter on top of a service layer.

┌─────────────────────────────────────────────────────────────┐ │ ShipMore Service Layer │ │ │ │ PageService MonetizationService LeadService │ │ TenantService MediaService AnalyticsService │ │ ComposerService ImportService ObservationsService │ │ │ │ (all business logic — uses Payload Local API) │ └──────────┬──────────┬──────────┬──────────┬──────────────────┘ │ │ │ │ ┌───────▼──┐ ┌────▼────┐ ┌──▼────┐ ┌─▼──────┐ │ MCP │ │ REST │ │ CLI │ │Webhooks│ │ plugin │ │(Payload │ │ binary│ │ + cron │ │ (agents) │ │ + app) │ │ │ │ │ └──────────┘ └─────────┘ └───────┘ └────────┘

When the ecosystem moves — new agent protocol, new tool, new platform — ShipMore extends an interface. Logic doesn’t get re-implemented per surface.

The four pillars

ShipMore is a self-hosted operating system for data products. Operators import a dataset; the platform handles four pillars without per-archetype customization:

PillarWhat it doesWhere it lives
PresentCards, detail pages, listings, searchdomains/cms/components/ + domains/present/orama/
MonetizeFeatured slots, gated content, paywall, four Stripe typesdomains/monetization/
MonitorPer-tenant observations, /operate dashboarddomains/monitor/
OperateRecords, schemas, imports, moderationPayload admin + CLI + MCP

Three-level access model

LevelInterfaceUsed by
1 — AgentMCP plugin (primary), CLI, RESTClaude Code, MCP-capable clients, scripted operators
2 — AdminPayload admin panelHumans reviewing, approving, auditing
3 — CodeNext.js + custom domain codeBespoke blocks, integrations

Agents create content as drafts by default; humans review and publish via the Payload admin. Every agent action is auditable and reversible through Payload’s version history.

Interface boundaries

All three Payload-native protocol surfaces (REST, MCP, GraphQL) run in-process on the ShipMore box. They are different protocols over the same service layer — not separate systems.

ShipMore box (Railway / Vercel) ├── REST API (Payload built-in) ─┐ ├── MCP server (plugin-mcp) ─┼──→ service layer → Payload Local API → MongoDB └── GraphQL (Payload built-in) ─┘ External clients └── shipmore CLI binary ──→ REST API ──→ ShipMore box

Tenant-facing public API (a tenant’s data product serving its own end users) lives in src/app/(frontend)/, completely separate from Payload’s /api/ namespace.

The agent boundary

ShipMore has no LLM. No model SDK, no API key, no inference calls inside the platform. The intelligence layer is the operator’s harness — Claude Code, Claude Desktop, any MCP client — driven by the SKILL.md shipped with the box.

The split:

  • ShipMore — deterministic primitives. Commands, endpoints, heuristic inference, storage. Produces structured facts.
  • Harness agent — all judgment. Reads raw data, interprets heuristic output, decides what to change.

SKILL.md teaches agents how to think, not what to decide. See Agents & CLI.

Architectural rules

Dependency injection. Domains expose pure services and utils. They do not import getPayload(), stripe, or other root-owned clients directly — those are instantiated at root (src/lib/) and passed in.

Server actions. All 'use server' actions live in src/actions/ only. Domains do not export action-generator functions.

Tenant scoping. The service layer receives tenantId as an argument. It does not resolve “which tenants can this user access?” — Payload RBAC handles that.

Logic lives once. Never put business logic inside MCP tool handlers, REST route handlers, or webhook handlers. Put it in the service layer.

Search boundary. Two separate search layers, kept isolated:

  • @payloadcms/plugin-search — global editorial search over posts. Do not add records to it.
  • Orama (src/domains/present/orama/) — per-tenant in-memory index over records only. Synchronous (v3+); registry is a Node module-level singleton — never use runtime = 'edge' on Present routes.

Operational constraints

The single-box, multi-tenant deployment model places several shared components on the same Node process:

  • Per-tenant Orama indexes live in-process. Scales with records-per-tenant × indexed-field size.
  • Import peak memory ≈ 2–3× file size. Capped by IMPORT_MAX_FILE_SIZE_MB (default 100 MB).
  • Payload Local API + Next.js baseline ≈ 250 MB.

Recommended box sizes:

TenantsReasonable baselineComfortable
11 GB2 GB
32 GB4 GB
5+reconsider single-box model