Skip to main content

Why versioning matters

Most hosts cache some of your MCP workload responses when your submission is accepted. Knowing which response is cached and which isn’t tells you whether a change ships immediately or needs a new submission. Cached at submission (frozen until you submit a new version):
  • tools/list response: tool descriptions, annotations and input/output schemas, on all workloads.
  • resources/read response: the views entrypoint HTML, on MCP apps specifically.
Everything else reaches your server on every user interaction (an actual tools/call, for example) and runs your latest deployment. This split is what makes versioning tricky: say you deploy an update that adds a required input parameter to a tool. The host still sources its input schema from the cached tools/list, so it fires a tools/call without the new parameter. However, your freshly deployed handler now expects it and rejects the request with a validation error. All calls to this tool now fail, with no possible recovery other than reverting your deployment. You just broke your production workload. In order to avoid this scenario, the following table summarizes how to handle the most common operations safely. All recommended migration paths keep host knowledge of your server and your actual service deployment compatible.

Operation-specific migration paths

For all workloads

For MCP apps specifically

We recommend using a dedicated environment for each version you submit to a host store. Because hosts freeze a snapshot of your workload at submission time, a dedicated environment with its dedicated URL gives you a stable, immutable target for each published version:
  • Your ongoing development on other environments never affects an already-submitted version.
  • You can keep shipping fixes to a submitted version (within the limits described above) without disturbing your next release.
  • When you want a clean break, you cut a new release against a new environment and submit it as a new version.
Name your environments after the released version (for example v1, v2) so it stays obvious which environment backs which submission on each host.

Serving multiple versions on one domain

Some hosts, like ChatGPT, require you to keep the same domain when you publish a new version of your plugin. In order to be able to use a dedicated environment per version, you should leverage custom domains and advertise multiple versions of your app. You can map several environments of the same project onto a single domain using path prefixes. Each version keeps its own immutable environment (and therefore its own frozen submission snapshot), while sharing one public subdomain. You can satisfy the same-domain requirement without disturbing your existing deployment. Configure this from your Project SettingsDomains tab with the domain edit button (see Routing multiple environments on one domain). For example, this is how Alpic routes its own MCP server:
Keep the route you advertise to end users on the root path (empty prefix), and reserve versioned prefixes (v1, v2, …) for host submissions. Point each prefix at the environment whose branch matches that version’s release line.

Asset naming strategy

For MCP apps, the resources/read response serves as the views entrypoint HTML. How your build pipeline bundles and references built assets directly determines your release strategy.
TL;DR: Use deterministic asset names within a released version and its fixes. This lets you ship small updates to your views by simply redeploying, while the host serves the latest asset content from the stable URL it already cached. Next and previous versions use version-specific asset names and aren’t affected.

Inline assets in HTML

All CSS and JS content is included in the resources/read content. You can’t update the view behavior without a new submission.

Assets bundled separately with unique names per build

Your build pipeline produces content-hashed names that change on every build (assets-fdsiucv7384.js on the first build, then assets-ytiroiu4253.js on the next one). The cached entrypoint HTML keeps pointing at the old name, so a new deployment exposes assets the host will never download, and the old assets keep being served. You can’t update the view behavior without a new submission.

Assets bundled separately with deterministic, stable names

Your build pipeline produces assets with names that stay the same across builds (always assets.js). The cached entrypoint HTML keeps pointing at the same URL, and a redeploy replaces the content served at that URL. Updated assets ship without resubmission: you control the application lifecycle through deployment, not submission. However, your changes then impact all existing views across all conversations.
If you use a single environment for all your workload versions, make sure to keep deterministic names only within a single released version scope. You would otherwise risk breaking all existing views. See details below.
Hosts don’t just render a view once. To rebuild the conversation as the user scrolls, they persist the view’s tool input, output, metadata and — depending on the host’s capabilities — its view state in the conversation history. When an old view re-enters the viewport (for example, the user scrolls back in time or reopens an old conversation), the host re-renders it by re-fetching the asset referenced in that turn’s entrypoint HTML. If you keep the same asset name across versions, that re-render pulls your latest asset code, but feeds it the old turn’s input/output/metadata. The new asset expects the updated sidecar tool input/output schemas, so it ends up rendering against data shaped for a previous version — breaking or corrupting views in past conversations that were perfectly valid when they were created. Giving each released version its own asset names keeps every historical turn pinned to the asset it was built against, so old views keep rendering correctly while new views get the new code.