npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

frameworc-mcp

v0.4.0

Published

MCP server for the FrameworC OctoberCMS plugin — programmatically create/edit FrameworC pages and blocks.

Readme

@yourorg/frameworc-mcp

A Model Context Protocol server that lets Claude / Cursor / opencode / other AI chat clients create and edit pages built with the FrameworC OctoberCMS plugin. One MCP process serves any number of OctCMS installs — you tell the chat which site to work on by calling use_site.

What it does

  • Composes 15 prebuilt FrameworC blocks (Header, Section, Tiles, Slider, Tabs, Accordion, Form, Gallery, Downloads, Columns, Prefill, BlogList, MenuBlock, ImageStrip, InstaFeed) into pages
  • Lists existing pages and their full builder JSON
  • Creates pages, adds / updates / removes / reorders individual blocks — on pages and on Prefill entries
  • Full CRUD for Form entries (incl. their field rows), Menu entries (incl. the navigation tree) and Prefill entries (incl. their builder blocks)
  • Extracts a repeated page section into a Prefill entry and replaces it with a reference (extract_block_to_prefill) — the intended FrameworC de-duplication workflow
  • Reads and writes the per-site singles (Meta & SEO, Navigation, Footer) and the global FrameworC settings (navbar options, SCSS variables; integration secrets are not accessible by design)
  • Multisite-aware: every content tool takes/pins a site_id; page and prefill translations are linked for the language switcher
  • Publishes the block catalogue as an MCP resource (frameworc://blocks) so the chat agent knows each block's fields, defaults, and when-to-use notes
  • Draft by default — created pages have is_enabled = false; the human flips the switch in the OctCMS backend after assigning images

The chat-side flow (text → blocks) is LLM-native: you paste a markdown page (or upload .docx / .pdf and instruct Claude to convert it to markdown), Claude reads the frameworc://blocks resource once, segments the content into blocks, calls list_forms / list_menus / list_prefills as needed, then calls create_page with the assembled JSON. The MCP simply forwards authenticated HTTPS calls to the OctCMS install.

Architecture

chat client (Claude Desktop / opencode / Cursor / Cline / Continue / ...)
  │ stdio JSON-RPC
  ▼
node dist/index.js         (local, spawned by the chat client)
  reads ~/.config/frameworc/sites.json (auto-created on first run; hot-reloaded)
  │ HTTPS + Authorization: Bearer <token resolved by URL>
  ▼
any OctCMS install         /api/mcp/v1/*  (provided by the crscompany/frameworcmcp plugin v1.1.0+)

No Docker, no ToolHive, no remote gateway, no second auth layer. Each OctCMS install has its own bearer token stored in its backend under Settings → FrameworC → MCP API, and your laptop keeps the URL → token map in one local file.

Install

git clone <your-gitlab-url>/frameworc-mcp.git
cd frameworc-mcp
npm install
npm run build

(Optional) publish to your GitLab group's npm registry so colleagues can npx it without a clone — see Publishing below.

Claude Desktop config

Edit claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/; Linux: ~/.config/Claude/):

{
  "mcpServers": {
    "frameworc": {
      "command": "npx",
      "args": ["-y", "@yourorg/frameworc-mcp"]
    }
  }
}

If you cloned instead of npx-installed, point args to your local dist/index.js:

{
  "mcpServers": {
    "frameworc": {
      "command": "node",
      "args": ["/absolute/path/to/frameworc-mcp/dist/index.js"]
    }
  }
}

No env vars. Restart Claude → the frameworc tools appear. On first start the MCP creates ~/.config/frameworc/sites.json with a template; edit it with your real sites (see below).

Per-site token file — ~/.config/frameworc/sites.json

The MCP stores one bearer token per OctCMS install in this file on your laptop. It's the only credential store. Format:

{
  "sites": [
    { "label": "Client A prod",     "url": "https://clienta.test",          "token": "AAA..." },
    { "label": "Client A staging",  "url": "https://staging.clienta.test",  "token": "BBB..." },
    { "label": "Myco",              "url": "https://myco.example",           "token": "CCC..." }
  ]
}
  • label is optional, shown by the list_sites tool so the chat can pick a site by name when you say "use the myco site".
  • url is matched after normalising trailing slashes — https://x.test/ and https://x.test are the same.
  • token is sent as Authorization: Bearer <token> on every HTTP call to that site. Never committed to git.

The file is created automatically on first MCP startup (with a template and 0600 perms). Edits are picked up via stat-on-every-tool-call hot-reload — no MCP or chat-client restart needed.

If the file is missing or empty, use_site errors with a pointer to where to add it. The list_sites tool enumerates configured sites without exposing tokens.

Adding a new OctCMS site

  1. Spin up the new OctCMS install with the FrameworC suite incl. the crscompany/frameworcmcp plugin (v1.1.0 or newer).
  2. Backend → Settings → FrameworC → MCP API → paste a freshly generated random string (e.g. openssl rand -hex 32) → Save.
  3. Edit ~/.config/frameworc/sites.json on your laptop, add one entry: { "label": "New Client", "url": "https://newsite.test", "token": "<that string>" }.
  4. In your next chat (no Claude Desktop restart needed): "Use https://newsite.test." → MCP hot-reloads the file, finds the token, sends it. Done.

No env var, no chat-client config edit, no ToolHive touch, no repo push. The whole token map lives in one file on your machine.

Security

  • The file should be 0600 (auto-set on first creation). If you chmod it looser, the MCP prints a stderr warning on startup.
  • A leak of the file compromises every site listed in it. Treat it like an SSH private key — back it up, rotate tokens periodically, never commit it to git.
  • One-off override: use_site("https://X", "token-string") lets you pass a token inline (without storing it) for the duration of the chat session. Useful for testing a token before saving it.

Tools (0.4.0)

| Tool | Description | |---|---| | use_site(url, site_token?) | Pin the target OctCMS site for the session. Token resolved from ~/.config/frameworc/sites.json by URL, or supplied via the optional site_token arg. | | list_sites | List configured sites (label + URL only, no tokens) from sites.json. | | list_cms_sites | List the multisite sites (languages) inside the pinned install. | | use_cms_site(site_id) | Pin the multisite site for subsequent calls (null = primary). | | list_pages | List all pages on the pinned site. | | get_page(id) | Full page JSON (page meta + builder blocks + nested repeaters). | | create_page(payload) | Create a draft page. payload = { page: {...}, builder: [...] }. | | update_page(id, payload) | Edit page meta, or full-rebuild the builder array (prefer the per-block tools). | | delete_page(id) | Soft-delete a page. | | create_translation(id, target_site_id, prefill?) | Linked sibling of a page (or Prefill with prefill:true) on another multisite site. | | add_block(page_id \| prefill_id, block, position?) | Append (or insert at position) a block. | | update_block(page_id \| prefill_id, block_id, block) | Replace one block by row id (new id returned; human-assigned media carried over unless the type changes). | | remove_block(page_id \| prefill_id, block_id) | Remove one block by row id. | | reorder_blocks(page_id \| prefill_id, order) | Reorder blocks (order = array of all row ids in new order). | | extract_block_to_prefill(page_id, block_id, title) | Move a page block into a new Prefill entry (lossless, media survives) and reference it in place. | | list_forms / get_form(id) | Form entries; get_form includes the fwcFields rows. | | get_form_schema | Live field-group catalogue for authoring forms. | | create_form / update_form / delete_form | Form CRUD. fwcFields rows: {group, label, name, required, width, ...}; delete guarded unless force:true. | | list_menus / get_menu(id) | Menu entries; get_menu includes the navigation tree. | | create_menu / update_menu / delete_menu | Menu CRUD. Tree items {title, url \| {page_id}, anchor, blank, children}, max 2 levels; delete guarded. | | list_prefills / get_prefill(id) | Prefill entries; get_prefill includes the builder blocks. | | create_prefill / update_prefill / delete_prefill | Prefill CRUD — same block shape as pages; delete guarded. | | get_page_meta(handle) / update_page_meta(handle, fields) | Per-site singles: Meta (SEO), Navigation (navbar incl. nav menu link + buttons), Footer (incl. socials rows + nav). | | get_settings / update_settings(fields) | Global FrameworC settings: navbar options + custom SCSS variables. Integration secrets are not exposed. | | get_block_schema(name) | Field schema + usage notes for one block (live from the CMS). |

Form and Menu entries have no translation linking — create them per site by passing site_id.

Resource

  • frameworc://blocks — reference for all 15 block types (fields, defaults, enums, conditional visibility, nested repeaters, reference fields). The chat reads this once per session; you don't invoke it manually.

Page-content JSON shape

create_page / update_page payload:

{
  "page": {
    "title": "Contact",
    "slug": "contact",
    "fullslug": "contact",
    "is_enabled": false,
    "metaTitle": "",
    "metaDescription": "",
    "menuStyle": "solid",
    "menuHide": "no"
  },
  "builder": [
    {
      "content_group": "Header",
      "base": {
        "blockId": "kontakt",
        "headline": "<h1>Contact us</h1>",
        "elevated": false,
        "containerWidth": "default",
        "backgroundColor": "default",
        "customCssClass": [],
        "responsiveHide": []
      },
      "content": {
        "image": "",
        "imageMobile": "",
        "isVideoBg": false,
        "buttonLabel1": "",
        "buttonLink1": "",
        "buttonBlank1": false,
        "fullHeight": true,
        "contrast": false,
        "overlay": false
      }
    },
    {
      "content_group": "Form",
      "base": { "blockId": "formular", "headline": "<h2>Write us</h2>" },
      "form": 7,
      "content": { "variant": "default" }
    }
  ]
}

Storage encoding (what the MCP understands)

  • Media fields (image, imageMobile, backgroundImage, buttonIcon1..4, ogImage, images, file) must be empty in the JSON — they are rejected if non-empty. Fill them in the OctCMS backend.
  • switch fields accept booleans; the API normalises to "1" / "0" for storage.
  • customCssClass + responsiveHide are arrays of strings.
  • Multi-mediafinder fields (images for Gallery / ImageStrip) are arrays of path strings — but must be empty [] per the rule above.
  • entries links are integers (or {id: n}): form (block level for Form), content.menu (for MenuBlock), content.block (for Prefill). Reads return them as {id, title}.
  • Slider accepts an optional content.breakpoints = { tablet: number, mobile: number }.
  • Columns blocks have content.columns = [{ blockId, builder: [...blocks] }] — recursive: each column's builder follows the exact same shape as the top-level builder.

Catalogue drift

The hand-authored block catalogue lives in src/catalogue.ts. It mirrors plugins/crscompany/frameworc/blueprints/Blocks/*.yaml + Mixins/Buttons.yaml + Mixins/Buttons2.yaml + BaseBlock.yaml. If a block blueprint changes (new field, new enum value, removed field), update src/catalogue.ts to match and rebuild. No generator script — kept manual because blocks change rarely per the FrameworC convention.

Publishing

To publish to your GitLab group's npm registry:

  1. Edit package.json:

    • Replace @yourorg in name and publishConfig["@yourorg:registry"] with your actual GitLab scope/group.
    • Replace <YOUR-PROJECT-ID> in publishConfig["@yourorg:registry"] with the numeric project id of the frameworc-mcp GitLab repo.
  2. Create a GitLab deploy token / project access token with api + write_registry scope. Add a .npmrc at the registry host:

    @yourorg:registry=https://gitlab.com/api/v4/projects/<YOUR-PROJECT-ID>/packages/npm/
    //gitlab.com/api/v4/projects/<YOUR-PROJECT-ID>/packages/npm/:_authToken=<TOKEN>
  3. npm publish.

Colleagues then use npx -y @yourorg/frameworc-mcp in their claude_desktop_config.json (no clone needed).

License

MIT.