Skip to content

refactor: Rename 'openai' mode to 'apps' for MCP Apps compatibility#720

Open
jirispilka wants to merge 7 commits intomasterfrom
claude/refactor-ui-handling-amyRL
Open

refactor: Rename 'openai' mode to 'apps' for MCP Apps compatibility#720
jirispilka wants to merge 7 commits intomasterfrom
claude/refactor-ui-handling-amyRL

Conversation

@jirispilka
Copy link
Copy Markdown
Collaborator

@jirispilka jirispilka commented Apr 19, 2026

🔗 PR chain — part of #577

This is step 1 of 6 in the #577 umbrella rollout of the MCP Apps decoupled pattern.

# Issue PR Status
1 #714 — Rename openaiapps #720 (this PR) approved
2 #715 — Capability auto-detect + gating #721 merged to #720 merged
3 #716fetch-actor-details split (pilot) #722 in review
4 #717search-actors split #723 in review
5 #718call-actor split #724 in review
6 #719get-actor-run split #734 in review

Closes #714.

Release coordination


Summary

Renames the 'openai' server mode to 'apps' throughout the codebase to align with the MCP Apps naming convention. The 'openai' string is retained as a deprecated alias in the external API (UiMode) for backward compatibility, with automatic normalization to 'apps' and a deprecation warning at ingestion.

Preparatory refactor — no behavior change. Later PRs in the chain build the decoupled-pattern architecture on top.

Key Changes

Type system & API surface

  • ServerMode: 'default' | 'openai''default' | 'apps'
  • UiMode (external): 'apps' | 'openai' (both accepted; 'openai' normalized with a warning)
  • New resolveServerMode(uiMode) in src/types.ts normalizes UiMode → canonical ServerMode
  • parseUiMode() accepts 'apps', 'true' (alias for 'apps'), 'openai' (deprecated alias)

Directory & file renames

  • src/tools/openai/src/tools/apps/ (all files, imports; git mv preserves history)
  • src/utils/server-instructions/openai.ts.../apps.ts
  • openaiActorExecutor/openaiCallActor/openaiSearchActors/openaiFetchActorDetails/openaiGetActorRunapps* equivalents

Server

  • ActorsMcpServer constructor: console.warn on uiMode === 'openai' (deprecation), then normalizes via resolveServerMode()
  • Using console.warn rather than log.warning — stdio mode sets log level to ERROR at startup, which would silence log.warning; console.warn always reaches stderr

Category & executor

  • toolCategories mode keys: openai:apps:
  • actorExecutorsByMode: uses 'apps'

Instructions & comments

  • Server instructions renamed (getOpenaiInstructionsgetAppsInstructions, "UI Mode" → "Apps Mode")
  • Code comments referencing "openai mode/variant" updated to "apps mode/variant"
  • Not renamed: openai/* _meta keys and their prefix references — those are real OpenAI Apps SDK spec keys (e.g. openai/widgetCSP, openai/outputTemplate) and must stay verbatim

Tests

  • Fixtures in unit + integration suites flipped to 'apps'
  • One stdio integration test repurposed to exercise the deprecated 'openai' alias path (documented in-test)

Test plan

  • npm run type-check passes
  • npm run lint passes
  • npm run test:unit passes (561/561)
  • mcpc probe matrix:
    • default (no flag): no -internal tools, no widget meta, no deprecation warning
    • --ui=true: -internal tools present, widget meta, no deprecation
    • UI_MODE=apps: same as --ui=true
    • UI_MODE=openai: same as apps mode + deprecation warning in stderr
    • UI_MODE=bogus: falls back to default mode
  • Integration tests (human-run)
  • apify-mcp-server-internal#454 lands after refactor: Auto-detect MCP Apps capability and gate server mode #721 merges — do not remove the uiMode/serverMode compat shims before that

https://claude.ai/code/session_01WDG6wb9JUB9Qb6fYTQ8G7y

claude added 2 commits April 18, 2026 22:50
MCP Apps is an open standard; 'openai' was a misleading name. Rename the
internal ServerMode and related file paths to 'apps' before any architectural
work on top (see #577).

- ServerMode = 'default' | 'apps' (was 'default' | 'openai')
- Rename src/tools/openai/ -> src/tools/apps/ (preserves git history)
- Rename src/utils/server-instructions/openai.ts -> apps.ts
- UiMode accepts both 'apps' (canonical) and 'openai' (deprecated alias).
  resolveServerMode() normalizes; ActorsMcpServer constructor logs a
  deprecation warning when 'openai' is used.
- Rename mode-scoped exports: openaiSearchActors/openaiFetchActorDetails/
  openaiCallActor/openaiGetActorRun/openaiActorExecutor -> apps* equivalents.

No behavior change. 561 unit tests pass, lint clean, type-check clean.
Deprecation alias verified by an existing stdio integration test.

Part of #577. Closes #714.
stdio.ts sets log level to ERROR at startup (to avoid leaking log output
into the JSON-RPC channel), which silenced log.warning. Switch the
deprecation notice to console.warn so it always reaches stderr regardless
of the ambient log level.

Verified via mcpc probe matrix:
- UI_MODE=openai prints "UI mode 'openai' is deprecated; use 'apps' instead."
- UI_MODE=apps and UI_MODE=true emit no warning
- Both produce identical tool lists and widget metadata

Follow-up to #714.
@jirispilka jirispilka marked this pull request as ready for review April 20, 2026 15:38
@jirispilka jirispilka requested a review from MQ37 April 20, 2026 15:38
Copy link
Copy Markdown
Contributor

@MQ37 MQ37 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Renames the server “UI mode” from openai to apps across core codepaths (types, tool categories, instructions, CLI/dev server parsing) and updates unit/integration tests accordingly.

Changes:

  • Introduces ServerMode constants (default / apps) and parseServerMode() (accepting legacy openai + true/false shorthands).
  • Renames OpenAI-mode tool implementations and wiring to apps* equivalents and updates instruction selection.
  • Updates tool-loading logic and tests to use apps mode terminology/keys.

Reviewed changes

Copilot reviewed 37 out of 37 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/utils.tools_loader.test.ts Updates loader tests to use apps mode
tests/unit/types.parse_server_mode.test.ts Adds coverage for parseServerMode() mappings
tests/unit/tools.search_actors.widget.response.test.ts Switches widget test import from openai* to apps*
tests/unit/tools.mode_contract.test.ts Renames mode-contract assertions to apps
tests/unit/tools.categories.test.ts Updates category tests for apps mode
tests/unit/tools.call_actor_common.test.ts Renames “openai” expectations to “apps” wording
tests/unit/mcp.utils.test.ts Updates resource tests to use apps mode
tests/integration/suite.ts Updates integration suite mode params + alias coverage
tests/helpers.ts Renames test client option uiModeserverMode (still passed via ?ui= / UI_MODE)
src/utils/tools_loader.ts Loads UI tools and injects get-actor-run based on apps mode
src/utils/tools.ts Strips widget meta when mode is not apps
src/utils/server-instructions/index.ts Switches instructions mapping from openaiapps
src/utils/server-instructions/apps.ts Renames instructions builder to getAppsInstructions() and updates wording
src/utils/actor_details.ts Updates widget payload comment to “apps variant”
src/types.ts Defines ServerMode constants + parseServerMode(); options now use serverMode
src/tools/structured_output_schemas.ts Updates comment reference from openai/apps/
src/tools/index.ts Uses ServerMode.DEFAULT as default argument and updates default-category resolution
src/tools/default/call_actor.ts Updates comment to reference non-apps stripping
src/tools/core/search_actors_common.ts Renames docs/comments to “apps variants”
src/tools/core/get_actor_run_common.ts Renames docs/comments to “apps variants”
src/tools/core/fetch_actor_details_common.ts Renames docs/comments to “apps variants”
src/tools/core/call_actor_common.ts Renames docs/comments to “apps variants”
src/tools/core/actor_tools_factory.ts Updates comment to reference non-apps stripping
src/tools/categories.ts Renames tool mode maps from openaiapps and updates docs
src/tools/apps/search_actors_internal.ts Updates internal-tool description to “apps mode”
src/tools/apps/search_actors.ts Renames tool export to appsSearchActors
src/tools/apps/get_actor_run.ts Renames tool export to appsGetActorRun
src/tools/apps/fetch_actor_details_internal.ts Updates internal-tool description to “apps mode”
src/tools/apps/fetch_actor_details.ts Renames tool export to appsFetchActorDetails
src/tools/apps/call_actor.ts Renames tool export/description constant to apps*
src/tools/apps/actor_executor.ts Renames executor export to appsActorExecutor
src/stdio.ts Parses --ui/UI_MODE via parseServerMode() and passes serverMode
src/resources/resource_service.ts Serves widgets only when mode is apps
src/mcp/utils.ts Defaults mode to ServerMode.DEFAULT in URL param tool resolution
src/mcp/server.ts Wires appsActorExecutor and normalizes legacy uiMode/openai via parseServerMode()
src/index_internals.ts Removes re-exports of prior mode parsing/types from ./internals
src/dev_server.ts Parses ?ui= / UI_MODE via parseServerMode() and passes serverMode
Comments suppressed due to low confidence (1)

src/index_internals.ts:44

  • src/index_internals.ts used to re-export the mode parsing/types (parseUiMode, SERVER_MODES, UiMode, ServerMode), but they were removed without an equivalent replacement. Since ./internals is a published entrypoint (package.json exports), consider re-exporting parseServerMode, SERVER_MODES, and ServerMode (and/or keeping deprecated aliases) to avoid a breaking change for consumers.
export {
    APIFY_FAVICON_URL,
    ApifyClient,
    getExpectedToolNamesByCategories,
    getServerCard,
    TTLLRUCache,
    actorNameToToolName,
    HelperTools,
    SERVER_NAME,
    SERVER_TITLE,
    defaults,
    getDefaultTools,
    addTool,
    getCategoryTools,
    toolCategoriesEnabledByDefault,
    type ActorStore,
    type ServerCard,
    type ToolCategory,
    processParamsGetTools,
    getActorsAsTools,
    getToolPublicFieldOnly,
    getUnauthEnabledToolCategories,
    unauthEnabledTools,

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -30,7 +30,7 @@ describe('search-actors with widget (openaiSearchActors)', () => {
it('returns widgetActors, _meta, and OpenAI-specific instructions and text', async () => {
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test case description still says "OpenAI-specific" even though the suite now targets apps mode. Updating the wording will avoid confusion when grepping for OpenAI-only behavior.

Copilot uses AI. Check for mistakes.
Comment thread src/tools/categories.ts Outdated
Comment on lines 145 to 146
* @param mode - Required. Use `'default'` or `'apps'`.
* Made explicit (no default value) to prevent accidentally serving wrong-mode tools.
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc says the mode param is "Made explicit (no default value)", but getCategoryTools currently has a default (ServerMode.DEFAULT). Either remove the default or update the comment so it matches the function signature.

Suggested change
* @param mode - Required. Use `'default'` or `'apps'`.
* Made explicit (no default value) to prevent accidentally serving wrong-mode tools.
* @param mode - Optional. Use `'default'` or `'apps'`.
* Defaults to `ServerMode.DEFAULT` when omitted.

Copilot uses AI. Check for mistakes.
Comment thread src/types.ts Outdated
* Defaults to `ServerMode.DEFAULT` when unset.
*/
uiMode?: UiMode;
serverMode?: ServerMode;
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActorsMcpServerOptions was changed from uiMode?: UiMode to serverMode?: ServerMode, but the constructor still accepts a legacy uiMode at runtime. This creates a TypeScript-level breaking change for programmatic callers (and contradicts the stated back-compat intent). Consider reintroducing uiMode?: string | ServerMode (marked @deprecated) or a dedicated deprecated UiMode alias so existing consumers compile while the runtime normalization remains.

Suggested change
serverMode?: ServerMode;
serverMode?: ServerMode;
/**
* @deprecated Use `serverMode` instead.
* Legacy alias kept for TypeScript backward compatibility with callers that still pass `uiMode`.
*/
uiMode?: string | ServerMode;

Copilot uses AI. Check for mistakes.
Comment thread src/mcp/server.ts
Comment on lines +135 to 142
// Constructor is an ingestion boundary for programmatic callers. Normalize via
// parseServerMode so that runtime-invalid values ('openai' alias, stray strings)
// and the legacy `uiMode` field name are accepted gracefully during the transition
// to the canonical `serverMode` API. Remove the `uiMode` fallback once internal
// consumers have migrated (see apify-mcp-server-internal#454).
const legacyUiMode = (options as { uiMode?: string }).uiMode;
this.serverMode = parseServerMode(options.serverMode ?? legacyUiMode) ?? ServerMode.DEFAULT;
this.actorExecutor = actorExecutorsByMode[this.serverMode];
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code normalizes the deprecated 'openai' value via parseServerMode(...) but does not emit any deprecation warning. If the intended behavior is to warn on 'openai' usage (as described in the PR), add a warning here when options.serverMode/legacy uiMode is 'openai'; otherwise, the PR description should be updated to match the silent normalization behavior.

Copilot uses AI. Check for mistakes.
jirispilka and others added 2 commits April 20, 2026 22:22
* refactor: Extend serverMode option to accept 'auto'

Add a single ServerModeOption type that extends ServerMode with 'auto'
for capability-driven resolution, instead of introducing a parallel
UiOverride tri-state field. parseServerMode now always returns a valid
ServerModeOption ('auto' for missing/unknown input), and a new
resolveServerMode helper folds in client-capability detection.

Prepares for the capability-detection wiring in the next commit. Runtime
behavior unchanged: at construction time clientSupportsUi is false, so
'auto' resolves to DEFAULT — the same effective mode as before.

Behavior change at ingestion boundaries: an unknown raw value (e.g.
UI_MODE=bogus) now maps to 'auto' instead of undefined + DEFAULT
fallback. The effective mode at construction is still DEFAULT, but once
the next commit wires capability detection, typos become auto-detect
instead of silent default.

* feat: Auto-detect MCP Apps capability via initialize request

When `options.serverMode` is `'auto'` (the default), resolve the final
mode from the client's `initialize` capabilities: pick `apps` iff the
client advertises the `io.modelcontextprotocol/ui` extension with the
widget MIME type, otherwise `default`. Explicit `'default'`/`'apps'`
values bypass detection entirely (used by apify-mcp-server-internal).

Tools are finalized BEFORE the InitializeResult is sent, so clients that
ignore `notifications/tools/list_changed` still see the correct tool set
on the first `tools/list`. Entry points register a deferred tools loader
that `prepareForInitialize` runs after the mode is resolved; the stdio
and SSE transports wrap `transport.onmessage` to await that method
before dispatching `initialize`, and the streamable HTTP path awaits it
inline before `connect`.

Also fixes four issues found in review of the prior iteration:

  - Unhandled promise rejections in the transport wrappers: the stdio
    and SSE `transport.onmessage` proxies now `.catch` async failures,
    close the transport, and log — instead of dropping the rejection
    on the floor and hanging the client forever.
  - Batch JSON-RPC initialize: the streamable HTTP handler extracts the
    actual initialize message from array bodies before passing it into
    `prepareForInitialize` and `initializeRequestData`, so capability
    detection and telemetry don't silently fall back to undefined.
  - Resource service frozen to preliminary mode: `createResourceService`
    now takes a `getMode` getter instead of a captured `mode` value, so
    widget listing and reads pick up the mode flip after
    `prepareForInitialize` resolves `'auto'` to `'apps'`.
  - Stale initialize instructions: after flipping the mode,
    `prepareForInitialize` refreshes the SDK's internal `_instructions`
    field so the InitializeResult returns apps-mode instructions (not
    the preliminary default-mode ones captured at construction).

* refactor: Remove mode-specific server instructions, unify guidance for all clients

* docs: Update clientSupportsUi JSDoc to reference setupInitializeHandler

Removed references to prepareForInitialize and setupCapabilityNegotiation,
both of which were deleted in 4d2903c when the initialize request handler
override replaced them.

* refactor: Split loadToolsFromInput into async fetch + sync compose

Separate mode-agnostic network work (Actor metadata fetch) from
mode-dependent composition (mode-specific internal variants, auto-injection,
dedup). Exports:

- fetchToolSources(input, client, actorStore?) — async, mode-agnostic
- composeTools(sources, mode) — sync, mode-dependent
- loadToolsFromInput(...) — unchanged signature, now a wrapper calling both

Behavior is unchanged. This sets up callers that want to pay the network
cost before the server mode is known — the composition step can then run
synchronously inside the initialize request handler once mode is resolved
(see src/mcp/server.ts#setupInitializeHandler).

Extracted computeActorNamesToLoad as a mode-agnostic helper: the 'is this
selector an Actor name' decision only depends on whether the selector is a
category name or an internal tool in any mode — both of which are
mode-independent lookups.

* docs: Explain initialize request handler vs oninitialized choice

Add a JSDoc block on setupInitializeHandler documenting why we override the
SDK's initialize *request* handler instead of using `server.oninitialized`.

Key points:
- The SDK does not block inbound requests until notifications/initialized has
  been processed; notification handlers dispatch fire-and-forget
  (see node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js).
- A tools/list can race past our oninitialized callback even if the body is
  synchronous, because tool registration and request dispatch run as
  separate microtasks.
- The MCP Apps spec recommends checking client capabilities before
  registering UI tools; doing this inside the initialize request/response
  cycle is the only approach that guarantees tools are finalized before
  InitializeResult is sent and before the first tools/list handler runs.

References:
- MCP Apps spec:
  https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx
- MCP base lifecycle:
  https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle

* refactor: Use ext-apps SDK getUiCapability instead of local constant

Drop the hardcoded MCP_APPS_EXTENSION_ID string and the hand-rolled
capability lookup in detectClientSupportsUi. Use
`@modelcontextprotocol/ext-apps/server`'s `getUiCapability` helper
instead — it's the canonical way per the MCP Apps spec and already
exported by the ext-apps package we depend on.

Switch tsconfig moduleResolution from "Node" (inherited from
@apify/tsconfig base) to "Bundler" so TypeScript honors ext-apps'
conditional exports for the `./server` subpath. This is a pure
resolver-behavior change; our source already uses `.js` extensions on
relative imports (required by either Node/NodeNext/Bundler).

References:
- ext-apps getUiCapability helper:
  https://github.com/modelcontextprotocol/ext-apps/blob/main/src/server/index.ts
- MCP Apps capability negotiation:
  https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx

* refactor: Collapse server-instructions/{common,index} into one file

The common.ts/index.ts split dates back to when instructions were mode-specific
(apps.ts + default.ts + common.ts template). After 4d2903c unified them,
index.ts became a one-line wrapper around getCommonInstructions(); the split
added nothing.

Inline the content into index.ts as getServerInstructions() and delete
common.ts. Also clean up two stale JSDoc references to the removed
prepareForInitialize method in stdio.ts and resource_service.ts.

* refactor: Rename composeTools to buildToolsForMode, unify input normalization, and clean up phase workflows

- Replaced `composeTools` with `buildToolsForMode` for clarity.
- Consolidated input normalization logic into `normalizeInput`, improving consistency.
- Renamed `computeActorNamesToLoad` to `resolveActorsToLoad`.
- Revised workflows by separating mode-agnostic and mode-dependent phases, ensuring better readability and maintainability.

* refactor: Replace deferred tools loader with mode-agnostic queuing

- Removed `setDeferredToolsLoader` in favor of queuing mode-agnostic sources via `loadToolsFromInput`, `loadToolsByName`, and `loadToolsFromUrl`.
- Added pending tool source tracking with a single flush point in the `initialize` request handler, enabling resolution of client capabilities before building the final tool set.
- Consolidated duplicate workflows around tool registration.
- Updated tests and documentation to reflect new loading logic.

* refactor: Rename variables and refine workflow for mode resolution

- Renamed `hasResolvedServerMode` to `serverModeResolved` for clarity and consistency.
- Replaced `pendingToolSources` with `pendingToolsAfterModeResolved`, storing only mode-agnostic inputs for improved tool composition.
- Updated `flushPendingToolSources` to `updateToolsAfterServerModeResolved`, aligning with new pending tools structure.
- Adjusted workflows to better handle mode-specific tool composition post-mode resolution.

* feat: Add serverMode configuration and clientCapabilities to MCP server tests

- Introduced `serverMode` configuration to `ActorsMcpServer` initialization in tests, defaulting to `ServerMode.DEFAULT`.
- Enhanced `createClientFn` to support `clientCapabilities` for advertising features during initialization.
- Added new tests for auto-detecting server modes (`apps` vs `default`) based on client UI capabilities.
- Updated helper methods to register client capabilities during connection setup.
- Refined test descriptions in capability gating scenarios for improved clarity.

* refactor: Improve pending tool handling and mode-dependent workflows

- Replace `pendingToolsAfterModeResolved` structure to capture actor tools per input for better tool composition isolation.
- Enhance workflows in `updateToolsAfterServerModeResolved` to fully reconcile mode-specific helpers during the final flush.
- Refactor server mode option parsing to explicitly handle raw and legacy fields.
- Refine widget handling guidance and clarify behavior for clients with/without UI capabilities.
- Update documentation to reflect adjusted workflows and renamed utility methods across phases.

* refactor: Improve widget handling logic and parameter initialization in server tools

- Refined guidance for widget-backed vs silent lookups based on client capabilities and follow-up workflows.
- Fixed parameter initialization in `transport.onmessage` to enforce proper structure for `ApifyRequestParams`.

* refactor: Enhance server instructions to be mode-aware and improve client handling
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t-ai Issues owned by the AI team. tested Temporary label used only programatically for some analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor: Rename 'openai' server mode to 'apps'

5 participants