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
| Operation | How to prepare | How to release | Notes |
|---|
| Add a new tool | Add the tool | Submit a new version | The tool stays invisible to the host until you submit. |
| Remove a tool | Add _meta.ui.visibility: ["app"] on your tool description | Submit a new version | Do 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 tool | Add the param in the input schema, but make it optional | Submit a new version | Do 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 tool | Keep changes backward compatible: relax validation, don’t tighten it, and avoid renaming. Widen accepted values rather than narrowing them | Submit a new version | Hosts 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 tool | Stop reading the param in your handler, but keep accepting it (leave it optional in the input schema) | Submit a new version | The 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
| Operation | How to prepare | How to release | Notes |
|---|
| Add a new view | Add the view resource and reference it from the relevant tool’s _meta.ui.resourceUri | Submit a new version | Both tools/list and resources/read are cached, so the host won’t discover the new view until you submit. |
| Update a view | To 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 HTML | The 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 view | Remove the view and stop referencing it from your tools’ _meta. Keep the referenced static assets available. | Submit a new version | The 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. |
Recommended release workflow
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.