mcp-use 1.x server by updating the runtime, server entry,
registrations, request context, security configuration, and widgets.
Most server registrations keep the same concepts and require small syntax
changes. Widgets require a complete rewrite as MCP App Views.
Migration checklist
Update the runtime and server entry
>=22.22.2, use ESM, import from mcp-use, and
default-export the server.Update server registrations
Update runtime behavior
Rebuild widgets as Views
Review v2 limitations and verify
Update the runtime and entry point
mcp-use v2 requires Node.js>=22.22.2 and ESM.
mcp-use:
index.ts.
v1 getHandler() remains only as a deprecated alias for server.fetch. Pass
server.fetch to edge and Fetch-based runtimes directly. To serve from a custom
Node.js http server, wrap the server with toNodeHandler():
Export every static tool
Assign every statically declared tool to an exported constant. If tools live in other modules, re-export them from the server entry:mcp-env.d.ts declaration registers
typeof import("<server-entry>"). Only exported ToolRef values participate in
View typing. This is a live type-only import, not the v1 tool-registry
generator. mcp-use dev, build, and typecheck create or refresh the
declaration, but they do not execute the server to generate tool-specific
types.
If the v1 server registers a tool from a loop, runtime configuration, or an
OpenAPI document, it cannot export a static ToolRef. Call that tool from a
View with explicit types:
Migrate tools
Move the callback to the second argument, replaceschema with
inputSchema, and return a raw MCP result.
schema remains an alias for inputSchema, but inputSchema matches the MCP
wire field and is the migration target.
Follow these result rules:
- A successful tool with
outputSchemamust return matchingstructuredContent. - An error result may return
isError: truewithout structured output. - For object-shaped structured output, include a text fallback in
content. - Replace
inputs[]with a Standard Schema-compatible input schema. - Do not chain
server.tool(...).tool(...);tool()returns aToolRef.
text(), object(), array(), image(), and
error() remain as deprecated upgrade shims. Prefer raw CallToolResult,
ReadResourceResult, and GetPromptResult envelopes. Native array() does not
preserve the v1 { data } wrapper.
Migrate resources and templates
Pass the reader as the second argument. A static resource callback receives(uri, ctx) and returns contents:
readCallback and callbacks fields.
For resource templates:
- replace nested
resourceTemplatewith top-leveluriTemplate - move
callbacks.completeto top-levelcomplete - pass the reader separately as
(uri, params, ctx) - handle inferred template values as
string | string[] - return raw
contents
Migrate prompts
Keepschema for prompt arguments, pass the callback separately, and return
raw messages:
args[] and inline cb. completable() supplies suggestions but
does not constrain valid values. Apply field refinements before wrapping a
schema with completable().
Update request context
v2 request context exists only for the current request. Remove session assumptions from client metadata, logging, progress, notifications, and elicitation.ctx.client.info(), capabilities(), can(), extension(), and user()
return client-declared request hints. Never use them for authorization. Use
verified ctx.auth data for identity and permissions.
Send notifications on the correct channel
v2 supports custom notifications during an active request:Make elicitation replay-safe
v2 elicitation returns an input-required result and re-runs the handler when the client supplies input:Rebuild widgets as MCP App Views
Legacy widgets require a full UI migration. File discovery, binding, tool results, component lifecycle, hooks, state, assets, and typing all changed.Move the files and binding
props to the tool result’s _meta field.
Access it from useToolContext().meta in the View. _meta is View-only and is
not validated or typed by outputSchema; validate or narrow it before use.
Keep structuredContent for output that matches outputSchema and should also
be visible to the model.
The bound tool must:
- export its
ToolRef - declare an
outputSchema - return matching
structuredContent - move v1
widget({ props })data to result_meta - return useful text
contentfor the model and text-only hosts - bind to exactly one View with
view: { name }
useCallTool().
Move resource facts such as description, csp, permissions, domain, and
prefersBorder into the bound tool’s view configuration. Remove v1
invoking, invoked, and widgetAccessible; the native View configuration
does not emit those OpenAI-specific overlay fields.
Replace widget hooks
Default-export the View component. It mounts once while the host connects and while tool input and result notifications arrive.error status also covers a host that cancels the tool call before a result
arrives. view.error is then a ToolCancelledError; check
view.error instanceof ToolError before reading view.error.result. Import both
classes from mcp-use/react.
Use the focused v2 surface:
viewConfig supports autoResize, displayModes, and
preferredDisplayMode. The values below are the defaults for autoResize and
displayModes, so omit the export when they are correct for the View:
displayModes must include "inline". preferredDisplayMode accepts
"inline" or "fullscreen", has no default, and must appear in
displayModes; ChatGPT may ignore it. See
resource display modes.
Use useSendSizeChanged() only when autoResize is false and the View
reports its size manually.
Compose optional presentation components such as ThemeProvider,
ViewControls, and your own error boundary. The framework owns the connection,
bootstrap, and required top-level error boundary.
Migrate View state and model context
useViewState() requires an object default and shares one JSON-serializable
snapshot across the mounted View. The state is model-visible.
ChatGPT can restore View state across View lifetimes. Other MCP Apps hosts keep
the state only for the current iframe lifetime.
Use the declarative <ModelContext> component for model-visible
natural-language context. v2 removes the imperative modelContext.set(),
modelContext.remove(), and modelContext.clear() helpers; render
<ModelContext> where the annotation applies so it follows React state and
component lifecycle. Use useSendFollowUp() to trigger a new model turn. Keep
presentation-only state in ordinary React state.
useFiles() keeps the upload/download capability check, but it no longer
accepts the v1 modelVisible option or mutates widget state automatically.
Update View assets
Native View documents are generated MCP resources. Do not registeruiResource() or serve a separate HTML document.
view.csp.
Set view.domain to the HTTPS UI origin for ChatGPT. Claude resource reads derive
the sandbox domain from the full MCP endpoint, using MCP_URL plus basePath or
the public request URL. Remove response helpers that overwrite generated
_meta.ui.domain. See UI origin and endpoint migration
for legacy URL values, proxy configuration, and metadata precedence.
Handle View URIs cached by ChatGPT
v1 served widgets atui://widget/<name>.html, and sometimes
ui://widget/<name>.<buildId>.html; v2 serves Views at exactly
ui://views/<name>.html. A published ChatGPT app caches the resource URI from
the v1 tool metadata and keeps requesting the old URI after you deploy v2, so
its Views fail to load until the app is resubmitted and re-approved with the
new metadata.
As a temporary bridge, add middleware that rewrites the legacy URI before the
request reaches the MCP handler and restores the requested URI in the
response:
ui://views/<name>.html
directly. Once the new submission is approved and live, remove the middleware.
Update authentication, proxies, and security
Native v2 acts as an OAuth resource server backed by an external authorization server. Import provider adapters frommcp-use/oauth/*, supply their required
locator options, and use the provider-specific ctx.auth.user type. For a
provider without Dynamic Client Registration, such as GitHub or Google,
oauthProxy makes the server its own authorization server. See
Migrate oauthProxy.
Replace generic userId assumptions with the provider’s mapped field, commonly
id. Replace getAuth, hasScope, and requireScope with verified
ctx.auth, scopes, permissions, and application authorization checks.
To serve public, optional, and sign-in tools from one OAuth-enabled endpoint,
set mixedAuth: true on the server and declare securitySchemes on each tool.
Without mixedAuth, every request to the MCP endpoint needs a valid token. If a
v1 tool sets _meta.securitySchemes by hand, v2 passes it through unchanged but
does not enforce it; declare securitySchemes on the tool instead. See
Mixed authentication.
jwksVerifier and mountOAuthProxy have no v2 equivalent.
v2 upstream composition accepts HTTP remotes or a ready compatible client
connection. A v1 stdio child-process proxy requires an architecture change.
Preserve security semantics explicitly
The nameallowedOrigins changed meaning:
allowMethods and allowHeaders to v2 methods and
allowedHeaders. Test Host rejection, Origin rejection, and CORS separately.
Use basePath for the MCP route. Set MCP_URL for the externally visible
server origin.
Keep existing OAuth connections working
v2 binds every access token to the MCP endpoint instead of the server origin. Clients that authorized against a v1 server can hold tokens that v2 rejects. Plan this migration before you deploy v2 to an existing production URL.aud is only the v1 identifier fails with Token audience does not include the protected resource, and the client receives HTTP 401. The v2
identifier always ends with basePath, so you cannot configure v2 to advertise
the bare origin.
Which clients are affected depends on where the client gets its resource
identifier:
- Clients that read protected-resource metadata follow the 401 challenge and request tokens for the v2 identifier when they next authorize.
- Clients with a saved resource setting keep requesting the v1 identifier. A published ChatGPT or Codex app uses the Resource value from its approved app submission. Reconnecting does not help, because every new token has the v1 audience. OpenAI tool scans that use the same saved value fail with the same error.
- Clients that refresh tokens get whichever audience your authorization
server issues for the
resourcethey send on refresh. A client that keeps sending the v1 identifier can keep getting v1-audience tokens indefinitely when refresh tokens rotate.
Find the identifier your v1 clients use
resource value in your v1 protected-resource metadata, or the
aud claim of a current access token. It is usually your MCP_URL origin.Accept both identifiers during the migration
createJwtVerifier through audience. The server
still advertises only the v2 identifier, and still verifies the signature,
issuer, and expiry.Update clients that save a resource setting
Allow the v2 identifier on the authorization server
validAudiences.Remove the v1 identifier after the last v1 token expires
aud does not include the v2 identifier. The
verified claims are available in authInfo.extra.payload. Remove the v1
identifier only after no v1-audience tokens appear for at least your longest
access-token lifetime.audience option checks the aud claim only. If your authorization server
also sets a resource claim to the v1 identifier, v2 still rejects the token.
Verify the migration with a real token that a v1 client holds.
The resource identifier is part of your public contract. Changing MCP_URL or
basePath later changes it again and requires the same migration.
Migrate oauthProxy
v2 keeps oauthProxy for providers without Dynamic Client Registration, such as
GitHub and Google, with a new API and a new token model:
- Import it from
mcp-use/oauth. Configure the provider withgithub(),google(), orupstream()instead ofauthEndpointandtokenEndpoint. - The proxy issues its own tokens. Tools read the upstream token with
upstreamToken(ctx.auth);verifyTokenandjwksVerifierare removed. - Outside localhost, the proxy requires a
secret(orMCP_USE_OAUTH_SECRET) and a sharedstorage. - The upstream callback URL moves from
/oauth/callbackto/mcp/oauth/callback. - Every user approves the consent page and signs in once more, in the same
client: v2 rejects the upstream tokens that v1 clients hold. Redirect the v1
/authorizeand/tokenpaths for clients that saved them.
oauthProxy
for the option mapping, a before-and-after example, and the deployment steps.
Review v2 limitations
The following v1 capabilities do not currently have direct v2 equivalents. Update the application design where one of these limitations affects your server.Server lifecycle and transport
- Session stores, active-session registries, stream managers, session recovery, and session affinity are not part of v2.
- Arbitrary session-targeted and post-response push are not available. Request-scoped custom notifications and subscription invalidations remain available.
- Blocking server-to-client sampling through
ctx.sample()is not available. Return aninputRequired.createMessage()round instead; see Sampling. - The v1 roots-changed callback surface is not available.
- Express and Connect adapters are not available. Use Hono,
server.fetch, ortoNodeHandler()frommcp-use/node. - Stdio serving and stdio child-process proxy configuration do not have a native migration path.
- CommonJS and unsupported Node.js versions are intentional compatibility breaks.
OAuth, proxy, and OpenAPI
oauthProxyis available for providers without Dynamic Client Registration, with a new API and its own tokens. A stand-alone authorization server with its own user accounts is not provided.mountOAuthProxy, the browser OAuth helper routes, has no equivalent.- Unsigned token bypasses such as
verifyJwt: falseare not supported. - Verify proxy requirements for upstream OAuth, templates, completions, subscriptions, list synchronization, sampling, and elicitation before migrating.
- Verify OpenAPI servers that depend on external references, cookies, non-JSON request bodies, callbacks, webhooks, or automatic security-scheme discovery.
Views
- Each View can bind to at most one server tool.
- A bound tool requires
outputSchemaand matchingstructuredContent. - Static typing sees exported
ToolRefvalues only. - Cross-lifetime
useViewState()restoration is host-specific. useFiles()is host-specific and must be capability-checked.- Raw
uiResource,@mcp-ui/serveradapters,exposeAsTool, and a shared View bound to multiple server tools have no native equivalent.
Verify the migration
Run a real TypeScript check
mcp-use typecheck, npm run typecheck, or the project’s actual tsc --noEmit command. mcp-use build bundles code but does not prove that v1
APIs are type-compatible.Build and start the production output
Exercise server primitives
Render every View state
Test security and authentication
Preserve behavioral coverage
Remove v1 leftovers
ctx.sample()
calls, stdio proxy configuration, and old security fields. No
mcp-use/server import can stay: that subpath does not exist in v2.Continue with the v2 guides
- Server overview explains the stateless server model.
- Tools, Resources, and Prompts cover each registration API.
- MCP Apps covers View authoring and host behavior.
- Authentication covers direct OAuth resource-server configuration.
- Notifications separates request-scoped messages from subscription invalidations.