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

optimizely-test-data-mcp

v0.2.0

Published

MCP server that turns a content plan into an importable Optimizely CMS 12 .episerverdata package.

Readme

optimizely-test-data-mcp

An MCP server that turns a content plan into an importable Optimizely CMS 12 .episerverdata package.

The generator never touches a CMS. It reads a content-type export and writes a file; a human exports the schema and a human imports the result under Admin → Import Data.

Use it

{
  "mcpServers": {
    "optimizely-test-data": {
      "command": "bunx",
      "args": ["[email protected]"]
    }
  }
}

Pin the version. An unpinned spec is re-resolved on every session start instead of being served from cache — on Windows that measured ~11s per start versus ~0.7s pinned.

npx works the same way if you would rather not use Bun.

Tools

| Tool | Purpose | |---|---| | load_schema | Load the site's content types from an Admin → Export Data export (content types only). Every other tool fails until this succeeds. | | usage_guide | Return the bundled guide for choosing values, understanding import behavior, and packaging Optimizely CMS test content. | | list_content_types | Find the types the site actually has, filtered by kind or name. | | query_schema | Bounded schema discovery with filters for type/property names, captions, help text, editor hints, edit-UI visibility, model existence, value kind, timestamps, and optional page-tree child availability. | | describe_content_type | One type's full property list, notes for export limitations, skeleton omission reasons, and a ready-to-edit plan skeleton for fillable fields. | | validate_plan | Check a plan without writing anything. Errors name the shape they expected; warnings flag supplied values that will not be applied, plus caveats about content that is still produced. | | build_package | Write the .episerverdata file, warning about dropped supplied values and the same content caveats. | | inspect_package | Summarise the content tree inside an existing package. |

Use query_schema before describe_content_type when you need to narrow a large export by metadata rather than dumping broad type lists into the prompt. Query results are capped and report total, truncated, and limit; the default limit is 50 and the maximum is 100.

Media

A media item is an ordinary plan item whose content type is a media type. Give it an image size and the generator writes a placeholder PNG into the package:

{
  "planId": "hero-layout-qa",
  "items": [
    { "key": "hero-img", "type": "ImageFile", "name": "hero.png",
      "image": { "width": 1600, "height": 900 } },

    { "key": "home", "type": "StandardPage", "name": "Home",
      "properties": { "HeroImage": { "ref": "hero-img" } } }
  ]
}

Referencing costs nothing: one media item pointed at by twenty pages is still one item and one file. Only count on the media item itself produces several. Images land in the global assets folder, and QA can replace one in the CMS afterwards — uploading over a placeholder is an ordinary upload and regenerates the thumbnail.

The bytes are always PNG whatever the item is named, because writing PNG bytes into a .jpg file would produce something that looks right and is not. Size is never guessed from the property: content-type exports carry no [ImageDescriptor], so state the size when the layout depends on aspect ratio. The default is 1280x720.

The image is a test pattern, not a flat colour, because a flat colour hides the bug you are looking for — squash one into the wrong aspect ratio and it still looks like itself. The circle is sized by the shorter side, so it turns into an ellipse the moment the ratio is not preserved; the grid cells are square for the same reason; the corner brackets go missing when the image is cropped; and the label names the size the plan asked for, so a wrong rendition shows without opening the asset.

Thumbnails are left empty on purpose. ImageData.Thumbnail is declared [ImageDescriptor(Width = 48, Height = 48, Pregenerated = true)], so the CMS generates one from the image; writing a placeholder would suppress that permanently.

Re-importing a plan

Content GUIDs derive from planId and each item's key, so importing the same plan twice updates the same content instead of creating a second copy. Verified against a real CMS 12: a second import duplicates nothing, and neither the media items nor their binaries are re-created.

Pick the same destination each time, or the content moves. Recognising the content by GUID is what lets the CMS update it — and the same recognition means that when the second import names a different destination, the existing items are relocated there rather than copied. The importer skips an item only when it is unchanged and already sits under the chosen destination; a different destination fails that test and the item is moved. Media is the exception: it is pinned to the global assets folder on every import, so pages move and images stay.

To create a genuinely separate set rather than updating the existing one, change the planId. Keys matter as much: because identity is the planId and key together, changing an item's key — including by changing which auto-generated key it receives — creates a duplicate rather than an update. Those two are the control, not the update existing content checkbox, which this package's identity mapping deliberately makes irrelevant.

Limits

Inline block properties, unpublished content, more than one language, Category and untyped Json are not generated. SVG, PDF and video media are not generated either — the CMS cannot rasterise them for a thumbnail. Any property type with no proven serialisation is left empty with a warning rather than guessed — a wrong guess produces content that looks right and is subtly incorrect.

describe_content_type lists every property, including fields that are hidden, not shown in CMS edit mode, or no longer present on the content model. Its skeleton omits only values the generator cannot serialize (block and unproven) and names the omission reason in-band.

The export does not contain enum labels/values, ContentArea [AllowedTypes], or [ScaffoldColumn] metadata. Integer fields therefore carry an advisory enum-label note in schema discovery for QA to confirm legal values in CMS edit mode; do not invent a value set. The parsed <availablecontenttypes> section is page-tree child availability only, not ContentArea [AllowedTypes]. editorHint values are reported verbatim from the export and are site-specific; their meaning varies by site and installed plugin.

Working: pages, blocks, nested page trees, text, HTML, ContentArea, content references, {{date:±Nd}} / {{date:±Nh}} / {{date:±Nm}} tokens, and PNG media.

Releasing

  1. Bump version in package.json.

  2. Commit, then tag and push:

    git tag v0.2.0
    git push origin v0.2.0

The workflow checks the tag against package.json, runs the tests, builds dist/, and publishes through OIDC trusted publishing — no token to store.

Releasing is CI's job. There is no prepublishOnly hook, so npm publish on its own does not build: dist/ is git-ignored, and publishing without it ships a package whose files: ["dist"] matches nothing. npm accepts that and exits 0, so nothing reports the failure — the broken version simply reaches consumers. If you ever do need to publish by hand, run bun run build first.

That hand-publish is required exactly once: npm can only trust a package it already knows about. Publish 0.1.2 manually (bun run build, then npm publish after npm login), then add the trusted publisher under the package's Settings → Trusted Publisher on npmjs.com, pointing at this repository and publish.yml. Every release after that comes from a tag.

Consumers pin the version they use — see the config above. Bumping here does not move them until they update that pin.