# Modyo MCP Agent Context

Critical rules and best practices for AI agents using Modyo MCP tools.

## Critical: Security & Liquid URLs

### 1. CSP Nonce (REQUIRED for inline scripts/styles)

**Rule**: ALWAYS use `nonce="{{csp_nonce}}"` on inline `<style>` and `<script>` tags.

Without this, inline code will be blocked by Content Security Policy.

```html
<!-- CORRECT -->
<style nonce="{{csp_nonce}}">
  .button { color: var(--bs-primary); }
</style>
<script nonce="{{csp_nonce}}">
  console.log('Initialized');
</script>

<!-- WRONG - Will be blocked -->
<style>
  .button { color: red; }
</style>
```

### 2. Relative URLs in Liquid

**Rule**: ALWAYS prefix internal links with `{{ site.url }}`.

**Good**: `{{ site.url }}/blog`, `{{ site.url }}/products`
**Bad**: `/blog`, `/products` (breaks in subpath deployments)

```liquid
<!-- CORRECT -->
<a href="{{ site.url }}/about">About Us</a>

<!-- WRONG - Breaks in subpath deployments -->
<a href="/about">About Us</a>
```

---

## Mental Model: 4 Independent Modules

Select tools based on the domain:

### 1. Content (Headless CMS)
- **Structure**: `Space` → `Type` → `Entry`
- **Assets**: Files/Images live in a `Space`, accessed via Assets
- **Key**: Content is data-centric
- **Tools**: `content-spaces-*`, `content-types-*`, `content-entries-*`

### 2. Channels (Frontend/Sites)
- **Structure**: `Site` → `Page` → `Widget`
- **Templates**: Reusable code (Snippets, CSS, JS)
- **Widgets**: Core UI building block
- **Key**: Always refer to widgets by `UUID` (stable), never `ID` (changes on publish)
- **Tools**: `channels-sites-*`, `channels-pages-*`, `channels-widgets-*`

### 3. Customers (Identity)
- **Structure**: `Realm` → `User`
- **Use for**: Authentication flows, user data management
- **Tools**: `customers-realms-*`, `customers-users-*`

### 4. Core (Platform Administration)
- **Use for**: Team members (admins), global configurations
- **Tools**: `core-users-*`, `core-groups-*`, `core-settings-*`

---

## Critical Safety Rules

### 1. Concurrency Locking

**Rule**: Before editing any `Page`, `Widget`, or `Template`, MUST acquire a lock using `channels-locks-manage`.

**Flow**:
1. `channels-locks-manage(action="acquire", lockable_type="...", lockable_id=...)`
2. Edit resource
3. `channels-locks-manage(action="release", lockId=...)`

**Why**: Prevents overwriting work if multiple users/agents are active.

### 2. Widget Identification

**Rule**: When manipulating widgets on a page, NEVER rely on the numeric `id`.

**Action**: Use the `uuid` of the widget instance. Numeric IDs change every time the site is published.

```typescript
// CORRECT
{
  action: "manage",
  widgetUuid: "550e8400-e29b-41d4-a716-446655440000"
}

// WRONG - ID changes on publish
{
  widgetId: 12345
}
```

### 3. Liquid Templating Safety

**Rule**: When accessing custom fields in Liquid, ALWAYS use bracket notation.

```liquid
<!-- CORRECT - Works with spaces and special chars -->
{{ entry.fields['product_name'] }}
{{ entry.fields['My Field'] }}

<!-- WRONG - Fails with spaces/special chars -->
{{ entry.fields.product_name }}
```

### 4. Destructive Actions

**Rule**: Always prefer `archive` or `unpublish` over `delete`.

**Requirement**: Check for dependencies (e.g., pages using a template) before deletion.

**Delete Confirmation**: All delete operations require typing the resource name to confirm. If cancelled, tools return `{ cancelled: true }` (not an error).

---

## Widget Type Decision

Choose the right widget type based on JavaScript needs:

| Need | Widget Type | Example Use Case |
|------|-------------|------------------|
| No `<script>` tag | `html` | Alerts, banners, Liquid content |
| Only snippets | `html` | `{% snippet 'header' %}` |
| JavaScript required | `custom_widget` | Vue/React apps, interactivity |

**Rule**: If your widget only uses HTML + CSS + Liquid (including snippets), use `type: "html"`. Only create a widget definition (`custom_widget`) when JavaScript is required.

**Why**: `html` widgets are simpler (inline, no publishing needed), while `custom_widget` requires creating + publishing a widget definition first.

---

## Page Content Strategy

| Scenario | Use | Why |
|----------|-----|-----|
| Interactive component with JavaScript | **Widget Definition** (`custom_widget`) | Proper encapsulation, reusable |
| Component on MULTIPLE pages | **Widget Definition** | Single source of truth |
| Page lists content entries | **Content Page** | Built for content rendering |
| Simple static content (<30 lines) | **HTML Block** (`type: "html"`) | No definition needed |

**Default to Widget Definitions for functional components.** HTML blocks are for simple static content only.

---

## Template Categories

| Category | Purpose | Liquid? | Reference |
|----------|---------|---------|-----------|
| `layout` | Page wrapper, must have `{{ content_for_layout }}` | Yes | One per site typically |
| `custom_snippet` | Reusable components | Yes | `{% snippet 'name' %}` |
| `system_snippet` | Built-in (head, header, footer) | Yes | Editable, NOT deletable |
| `stylesheet` | CSS, served from CDN | **No** | See example below |
| `javascript` | JS, served from CDN | **No** | See example below |

**Liquid references for stylesheet/javascript:**
```liquid
{{ 'name' | asset_url: 'css' | stylesheet_tag }}
{{ 'name' | asset_url: 'js' | script_tag }}
```

---

## Liquid Navigation Reference

```liquid
{% for item in menus['menu-slug'].visible_items %}
  <a href="{{ item.url }}" {% if item.url == page.url %}class="active"{% endif %}>
    {{ item.label }}
  </a>
  {% if item.child_items %}
    {% for child in item.child_items %}
      <a href="{{ child.url }}">{{ child.label }}</a>
    {% endfor %}
  {% endif %}
{% endfor %}
```

**Properties**: `item.label`, `item.url`, `item.parameterized_label`, `item.child_items`

---

## Token-First CSS Design

1. **Always read `root.css` first** before writing CSS (use `channels-templates-find` with category=stylesheet)
2. **Override Bootstrap tokens** in `root.css`: `:root { --bs-primary-rgb: R,G,B; }`
3. **Use `base.css`** only for rules Bootstrap doesn't cover
4. **In HTML**, prefer Bootstrap utilities (`bg-dark`, `text-white`, `py-5`)
5. **Key tokens**: `--bs-primary-rgb`, `--bs-body-font-family`, `--bs-body-color-rgb`

### CSS Token Patterns

Use `channels-templates-find` with category=stylesheet to find specific tokens:

| Pattern | Purpose | Example |
|---------|---------|---------|
| `--bs-primary` | Primary colors | `--bs-primary-rgb: 13,110,253` |
| `--bs-secondary` | Secondary colors | `--bs-secondary-rgb: 108,117,125` |
| `--bs-body` | Body (color, bg, font) | `--bs-body-color-rgb: 33,37,41` |
| `--bs-font` | Font families | `--bs-font-sans-serif` |
| `--bs-border` | Borders and radius | `--bs-border-radius: 0.375rem` |
| `--bs-link` | Link colors | `--bs-link-color-rgb` |
| `--bs-btn` | Button styles | `--bs-btn-*` |

---

## Grid Types and Column Mappings

| Grid Type | Columns | Use Case |
|-----------|---------|----------|
| `full_grid` | 0 | Single column |
| `full_two_cols_grid` | 0, 1 | Two equal columns |
| `full_three_cols_grid` | 0, 1, 2 | Three equal columns |
| `side_left_grid` | 0, sidebar | Main + left sidebar |
| `side_right_grid` | 0, sidebar | Main + right sidebar |

**has_router dual behavior**:
- **Widget Pages**: Client-side JS routing (SPAs)
- **Content Pages**: Server-side slug routing (`/blog/{slug}`)

**Widget sync parameter**:
- `sync: true` → Load immediately (critical content, above fold)
- `sync: false` → Load async (non-critical, improves performance)

---

## API Quirks & Gotchas

- **Home Page Path**: The path for the home page is an empty string `''`. Do NOT use `/home`, `home`, or `index`.
- **Path Formatting**: Never start a path with `/`. Use `about-us`, not `/about-us`.
- **Locales**: Native support for `es`, `en`, `pt`. For other languages (e.g., French), use the Liquid filter: `{{ entry.title | by_lang: "fr" }}`.
- **List vs Detail**: Some fields (like full content bodies) are excluded from `list` operations to save bandwidth. Always use `get` to retrieve full details.

---

## Common Workflows

### 1. Updating Widget Code

1. **Find**: `channels-widgets-manage(siteId=..., action="list", query="MyWidget")` to get the ID
2. **Lock**: `channels-locks-manage(action="acquire", lockable_type="WidgetDefinition", lockable_id=ID)`
3. **Backup**: `channels-widgets-manage(siteId=..., action="manage", identifier=ID)` (Store code in memory)
4. **Update**: `channels-widgets-manage(siteId=..., action="manage", identifier=ID, html="...", css="...", js="...")`
5. **Release**: `channels-locks-manage(action="release", lockId=LOCK_ID)`

### 2. Creating Content

1. **Check Type**: `content-types-get(spaceId=..., identifier=...)` to understand schema and required fields
2. **Create/Update**: Use `content-entries-upsert(spaceId=..., typeId=..., slug=..., ...)` for idempotency
3. **Publish**: Content is created in draft. Call `content-entries-manage(spaceId=..., action="publish", identifier=SLUG)` to make it live

### 3. Promoting Changes (Dev → Prod)

1. **Export**: Use `content-spaces-copy` or similar copy tools if available
2. **Manual Sync**: Read from Source (e.g., staging) and write to Target (e.g., production) using respective `upsert` tools to ensure IDs/Slugs match

---

## Search & Filtering

- Most `list` tools support a `query` parameter. Use it to filter by name/slug.
- Pagination is enabled by default (`page=1`). Always check `total` in response to see if you need to fetch more pages.

---

## Tool Naming Pattern

All tools follow the pattern: `{module}-{resource}-{action}`

Examples:
- `channels-widgets-manage`
- `content-entries-upsert`
- `customers-users-manage`
- `core-groups-manage`

Use `list` actions first to discover resource IDs before performing operations.
