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

OperationHow to prepareHow to releaseNotes
Add a new toolAdd the toolSubmit a new versionThe tool stays invisible to the host until you submit.
Remove a toolAdd _meta.ui.visibility: ["app"] on your tool descriptionSubmit a new versionDo not remove your tool altogether. It is still referenced in the cached tools/list on hosts and will keep being called by the model until you submit a new version. You can safely remove it after this revised version has been submitted.
Add a new param in an existing toolAdd the param in the input schema, but make it optionalSubmit a new versionDo not add a required param. Your handler is updated right away with the new requirement, but hosts won’t be aware of it until a new release is made and will fail all input validation on your tool.
Update a param in an existing toolKeep changes backward compatible: relax validation, don’t tighten it, and avoid renaming. Widen accepted values rather than narrowing themSubmit a new versionHosts validate inputs against the cached schema. If you tighten validation (e.g. make an optional param required, add new constraints) before resubmitting, the host keeps sending values your handler now rejects.
Remove a param in an existing toolStop reading the param in your handler, but keep accepting it (leave it optional in the input schema)Submit a new versionThe model keeps sending the param while the host references the cached tools/list. Removing it from the input schema before resubmitting can cause input validation to fail.

For MCP apps specifically

OperationHow to prepareHow to releaseNotes
Add a new viewAdd the view resource and reference it from the relevant tool’s _meta.ui.resourceUriSubmit a new versionBoth tools/list and resources/read are cached, so the host won’t discover the new view until you submit.
Update a viewTo change view behavior or styling only, ship new assets under stable, deterministic names (see Asset naming strategy)Update the assets content. You only need a new submission to change the view entrypoint HTMLThe resources/read entrypoint HTML is cached. With deterministic asset names you can ship updated assets without resubmitting, because the cached HTML keeps pointing at the same asset URLs.
Remove a viewRemove the view and stop referencing it from your tools’ _meta. Keep the referenced static assets available.Submit a new versionThe view is still referenced by the cached tools/list content, and the view content itself is still cached by the host. Removing them from your workload before you submit a new version therefore has no effect on the host. Just make sure the external public assets referenced by the view stay available.
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.

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.