diff --git a/.changeset/remove-protocol-era.md b/.changeset/remove-protocol-era.md new file mode 100644 index 000000000..11bf253b1 --- /dev/null +++ b/.changeset/remove-protocol-era.md @@ -0,0 +1,5 @@ +--- +"agent-bundle": minor +--- + +Remove `protocolEra` from `DevRuntimeMcpConnectionState` (exported from `agent-bundle/api`) and from the dev server's runtime MCP session snapshots. The MCP client never reported a protocol era, so the field was always absent; runtime MCP App previews no longer refuse a live session for lacking it. (#836) diff --git a/examples/rsc-agent-runtime/src/dev/rsbuild-runtime-session.ts b/examples/rsc-agent-runtime/src/dev/rsbuild-runtime-session.ts index 06d67ea3d..3154588bf 100644 --- a/examples/rsc-agent-runtime/src/dev/rsbuild-runtime-session.ts +++ b/examples/rsc-agent-runtime/src/dev/rsbuild-runtime-session.ts @@ -800,7 +800,6 @@ export class RsbuildRuntimeSession implements DevRuntimeSession { resources: Object.freeze({}), tools: Object.freeze({}), }), - protocolEra: 'modern', protocolVersion: '2025-06-18', server: Object.freeze({ name: projectName, version: projectVersion }), }); diff --git a/packages/agent-bundle/src/dev/mcp-app-runtime-preview-service.ts b/packages/agent-bundle/src/dev/mcp-app-runtime-preview-service.ts index 3b382841c..305322864 100644 --- a/packages/agent-bundle/src/dev/mcp-app-runtime-preview-service.ts +++ b/packages/agent-bundle/src/dev/mcp-app-runtime-preview-service.ts @@ -365,7 +365,7 @@ export class McpAppRuntimePreviewService implements McpAppRuntimeRoutePreviewSer throw new Error('Runtime MCP App stable session has foreign provider/state authority.'); } if ( - live.connection.protocolEra === undefined || live.connection.protocolVersion === undefined || + live.connection.protocolVersion === undefined || live.connection.capabilities === undefined || live.connection.server === undefined ) { throw new Error('Runtime MCP App stable session has incomplete negotiation.'); diff --git a/packages/agent-bundle/src/dev/mcp-session/mcp-session-types.ts b/packages/agent-bundle/src/dev/mcp-session/mcp-session-types.ts index fa606aebf..38df5e019 100644 --- a/packages/agent-bundle/src/dev/mcp-session/mcp-session-types.ts +++ b/packages/agent-bundle/src/dev/mcp-session/mcp-session-types.ts @@ -52,7 +52,6 @@ export interface McpClient { getServerCapabilities(): ServerCapabilities | undefined; getServerVersion(): Implementation | undefined; getNegotiatedProtocolVersion?(): string | undefined; - getProtocolEra?(): 'legacy' | 'modern' | undefined; listPrompts(params?: undefined, options?: McpRequestOptions): Promise<{ readonly prompts: readonly Prompt[] }>; listResources(params?: undefined, options?: McpRequestOptions): Promise<{ readonly resources: readonly Resource[] }>; listResourceTemplates( @@ -146,7 +145,6 @@ export interface McpSessionResourceOptions extends McpSessionRequestOptions { export interface McpSessionConnectionState { readonly capabilities: ServerCapabilities | undefined; - readonly protocolEra: 'legacy' | 'modern' | undefined; readonly protocolVersion: string | undefined; readonly server: Implementation | undefined; } diff --git a/packages/agent-bundle/src/dev/mcp-session/mcp-session.ts b/packages/agent-bundle/src/dev/mcp-session/mcp-session.ts index f4c2e0a11..2186e3b1b 100644 --- a/packages/agent-bundle/src/dev/mcp-session/mcp-session.ts +++ b/packages/agent-bundle/src/dev/mcp-session/mcp-session.ts @@ -640,7 +640,6 @@ export class McpSession { this.#capture = connectState.capture; this.#connection = Object.freeze({ capabilities: client.getServerCapabilities(), - protocolEra: client.getProtocolEra?.(), protocolVersion: client.getNegotiatedProtocolVersion?.(), server: client.getServerVersion(), }); diff --git a/packages/agent-bundle/src/dev/runtime-mcp-registry.ts b/packages/agent-bundle/src/dev/runtime-mcp-registry.ts index 03a6ccd0d..dcba6a3da 100644 --- a/packages/agent-bundle/src/dev/runtime-mcp-registry.ts +++ b/packages/agent-bundle/src/dev/runtime-mcp-registry.ts @@ -1154,7 +1154,6 @@ const bindingCopy = (binding: DevRuntimeMcpSessionBinding): DevRuntimeMcpInvalid const finiteConnectionState = (input: DevRuntimeMcpConnectionState): DevRuntimeMcpConnectionState => Object.freeze({ capabilities: input.capabilities === undefined ? undefined : jsonObject(input.capabilities, 'Runtime MCP connection capabilities'), - protocolEra: input.protocolEra, protocolVersion: input.protocolVersion, server: input.server === undefined ? undefined : Object.freeze({ name: nonempty(input.server.name, 'Runtime MCP connection server name'), diff --git a/packages/agent-bundle/src/dev/runtime-mcp-routes.ts b/packages/agent-bundle/src/dev/runtime-mcp-routes.ts index dff8203dd..295bc92a4 100644 --- a/packages/agent-bundle/src/dev/runtime-mcp-routes.ts +++ b/packages/agent-bundle/src/dev/runtime-mcp-routes.ts @@ -132,7 +132,6 @@ const publicSessionSnapshot = (snapshot: DevRuntimeMcpSessionSnapshot): PublicRu }), connection: Object.freeze({ capabilities: snapshot.connection.capabilities, - protocolEra: snapshot.connection.protocolEra, protocolVersion: snapshot.connection.protocolVersion, server: server === undefined ? undefined : Object.freeze({ name: server.name, version: server.version }), }), diff --git a/packages/agent-bundle/src/dev/runtime-protocol.ts b/packages/agent-bundle/src/dev/runtime-protocol.ts index 43f9188f6..52e1f4348 100644 --- a/packages/agent-bundle/src/dev/runtime-protocol.ts +++ b/packages/agent-bundle/src/dev/runtime-protocol.ts @@ -223,7 +223,6 @@ export interface DevRuntimeMcpRegistrySnapshot { export interface DevRuntimeMcpConnectionState { readonly capabilities: JsonObject | undefined; - readonly protocolEra: 'legacy' | 'modern' | undefined; readonly protocolVersion: string | undefined; readonly server: Readonly<{ readonly name: string; readonly version: string }> | undefined; } diff --git a/packages/agent-bundle/tests/dev-server.test.ts b/packages/agent-bundle/tests/dev-server.test.ts index b4a8053d4..c41e4b8c1 100644 --- a/packages/agent-bundle/tests/dev-server.test.ts +++ b/packages/agent-bundle/tests/dev-server.test.ts @@ -932,7 +932,7 @@ it('applies the established foreground origin and token guard to MCP session cre opens.push(options); return { binding: { epochId: 'epoch-a', serverName: 'weather', target: 'portable' }, - connection: { capabilities: {}, protocolEra: 'modern', protocolVersion: '2025-11-25', server: { name: 'fixture', version: '1.0.0' } }, + connection: { capabilities: {}, protocolVersion: '2025-11-25', server: { name: 'fixture', version: '1.0.0' } }, id: 'session-a', }; }, @@ -971,7 +971,7 @@ it('applies the established foreground origin and token guard to MCP session cre await expect(accepted.json()).resolves.toEqual({ session: { binding: { epochId: 'epoch-a', serverName: 'weather', target: 'portable' }, - connection: { capabilities: {}, protocolEra: 'modern', protocolVersion: '2025-11-25', server: { name: 'fixture', version: '1.0.0' } }, + connection: { capabilities: {}, protocolVersion: '2025-11-25', server: { name: 'fixture', version: '1.0.0' } }, id: 'session-a', }, }); @@ -986,7 +986,7 @@ it('accepts headerless browser same-origin fetch provenance with the exact token let streamSubscriptions = 0; const session = { binding: { epochId: 'epoch-a', serverName: 'weather', target: 'portable' }, - connection: { capabilities: {}, protocolEra: 'modern', protocolVersion: '2025-11-25', server: { name: 'fixture', version: '1.0.0' } }, + connection: { capabilities: {}, protocolVersion: '2025-11-25', server: { name: 'fixture', version: '1.0.0' } }, id: 'session-a', subscribeTrace: () => { streamSubscriptions += 1; @@ -1120,7 +1120,7 @@ it('ends active authenticated MCP trace readers before foreground shutdown destr let subscriptions = 0; const session = { binding: { epochId: 'epoch-a', serverName: 'weather', target: 'portable' }, - connection: { capabilities: {}, protocolEra: 'modern', protocolVersion: '2025-11-25', server: { name: 'fixture', version: '1.0.0' } }, + connection: { capabilities: {}, protocolVersion: '2025-11-25', server: { name: 'fixture', version: '1.0.0' } }, id: 'session-a', subscribeTrace: () => { subscriptions += 1; diff --git a/packages/agent-bundle/tests/mcp-app-runtime-binding-service.test.ts b/packages/agent-bundle/tests/mcp-app-runtime-binding-service.test.ts index 22fd46bc5..f4b30a33b 100644 --- a/packages/agent-bundle/tests/mcp-app-runtime-binding-service.test.ts +++ b/packages/agent-bundle/tests/mcp-app-runtime-binding-service.test.ts @@ -39,7 +39,7 @@ const runVector: RuntimeVector = Object.freeze({ const sessionSnapshot = (): DevRuntimeMcpSessionSnapshot => Object.freeze({ binding: Object.freeze({ ...runBinding, providerSessionId: 'provider-private', stateStoreId: 'state-private' }), - connection: Object.freeze({ capabilities: undefined, protocolEra: 'modern', protocolVersion: '2026-01-26', server: undefined }), + connection: Object.freeze({ capabilities: undefined, protocolVersion: '2026-01-26', server: undefined }), state: 'ready', }); diff --git a/packages/agent-bundle/tests/mcp-app-runtime-preview-service.test.ts b/packages/agent-bundle/tests/mcp-app-runtime-preview-service.test.ts index deabb2450..342893bfe 100644 --- a/packages/agent-bundle/tests/mcp-app-runtime-preview-service.test.ts +++ b/packages/agent-bundle/tests/mcp-app-runtime-preview-service.test.ts @@ -51,7 +51,6 @@ const snapshot = (): DevRuntimeMcpSessionSnapshot => Object.freeze({ }), connection: Object.freeze({ capabilities: Object.freeze({ resources: Object.freeze({}), tools: Object.freeze({}) }), - protocolEra: 'modern' as const, protocolVersion: '2026-01-26', server: Object.freeze({ name: 'weather', version: '1.0.0' }), }), diff --git a/packages/agent-bundle/tests/mcp-session-routes.test.ts b/packages/agent-bundle/tests/mcp-session-routes.test.ts index b2613cc4f..a2db8a968 100644 --- a/packages/agent-bundle/tests/mcp-session-routes.test.ts +++ b/packages/agent-bundle/tests/mcp-session-routes.test.ts @@ -29,7 +29,7 @@ const startRoutes = async (service: McpSessionRouteService): Promise '2026-07-28', - getProtocolEra: () => 'modern' as const, getPrompt: async () => ({ messages: [] }), getServerCapabilities: () => ({ logging: {} }), getServerVersion: () => ({ name: 'wire-fixture', version: '1.0.0' }), @@ -1695,7 +1694,6 @@ it('exposes one opaque, epoch-bound session handle with a bounded ordered wire t expect(session.binding).toEqual({ epochId: 'epoch-wire', serverName: 'fixture', target: 'portable' }); expect(Object.isFrozen(session.binding)).toBe(true); expect(session.connection).toMatchObject({ - protocolEra: 'modern', protocolVersion: '2026-07-28', server: { name: 'wire-fixture', version: '1.0.0' }, }); diff --git a/packages/agent-bundle/tests/runtime-mcp-registry.test.ts b/packages/agent-bundle/tests/runtime-mcp-registry.test.ts index 28d0668de..3d810b912 100644 --- a/packages/agent-bundle/tests/runtime-mcp-registry.test.ts +++ b/packages/agent-bundle/tests/runtime-mcp-registry.test.ts @@ -39,7 +39,6 @@ const deferred = (): Readonly<{ const connectionState: DevRuntimeMcpConnectionState = Object.freeze({ capabilities: Object.freeze({ tools: Object.freeze({}) }), - protocolEra: 'modern', protocolVersion: '2025-06-18', server: Object.freeze({ name: 'fixture', version: '1.0.0' }), }); diff --git a/packages/agent-bundle/tests/runtime-mcp-routes.test.ts b/packages/agent-bundle/tests/runtime-mcp-routes.test.ts index 2dd18d028..ac685ea78 100644 --- a/packages/agent-bundle/tests/runtime-mcp-routes.test.ts +++ b/packages/agent-bundle/tests/runtime-mcp-routes.test.ts @@ -25,7 +25,6 @@ it('delegates the fixed manual runtime MCP open route without becoming an App pr }), connection: Object.freeze({ capabilities: Object.freeze({}), - protocolEra: 'modern' as const, protocolVersion: '2026-01-26', server: Object.freeze({ name: 'weather', providerSessionId: 'provider-private', stateStoreId: 'state-private', unexpected: 'unexpected-value', version: '1.0.0' }), }), @@ -38,7 +37,6 @@ it('delegates the fixed manual runtime MCP open route without becoming an App pr }), connection: Object.freeze({ capabilities: Object.freeze({ tools: Object.freeze({ listChanged: true }) }), - protocolEra: 'modern' as const, protocolVersion: '2026-02-09', server: Object.freeze({ name: 'weather-next', providerSessionId: 'provider-private', stateStoreId: 'state-private', unexpected: 'unexpected-value', version: '2.0.0' }), }), @@ -92,7 +90,7 @@ it('delegates the fixed manual runtime MCP open route without becoming an App pr expect(opened).toEqual({ session: { binding: { definitionDigest: 'definition-a', registryRevision: 3, serverDigest: 'server-a', serverName: 'weather', sessionId: 'session-a', sessionRevision: 2, target: 'portable', transportDigest: 'transport-a', - }, connection: { capabilities: {}, protocolEra: 'modern', protocolVersion: '2026-01-26', server: { name: 'weather', version: '1.0.0' } }, state: 'ready' } }); + }, connection: { capabilities: {}, protocolVersion: '2026-01-26', server: { name: 'weather', version: '1.0.0' } }, state: 'ready' } }); expect(JSON.stringify(opened)).not.toContain('provider-private'); expect(JSON.stringify(opened)).not.toContain('state-private'); @@ -113,7 +111,7 @@ it('delegates the fixed manual runtime MCP open route without becoming an App pr definitionDigest: 'definition-b', registryRevision: 4, serverDigest: 'server-b', serverName: 'weather', sessionId: 'session-a', sessionRevision: 3, target: 'portable', transportDigest: 'transport-b', }, - connection: { capabilities: { tools: { listChanged: true } }, protocolEra: 'modern', protocolVersion: '2026-02-09', server: { name: 'weather-next', version: '2.0.0' } }, + connection: { capabilities: { tools: { listChanged: true } }, protocolVersion: '2026-02-09', server: { name: 'weather-next', version: '2.0.0' } }, state: 'ready', }, }); @@ -136,7 +134,7 @@ it('claims manual runtime MCP routes before the generic runtime browser API', as snapshot: () => Object.freeze({ binding: Object.freeze({ definitionDigest: 'definition-a', providerSessionId: 'provider-private', registryRevision: 3, serverDigest: 'server-a', serverName: 'weather', sessionId: 'session-a', sessionRevision: 2, stateStoreId: 'state-private', target: 'portable', transportDigest: 'transport-a', - }), connection: Object.freeze({ capabilities: Object.freeze({}), protocolEra: 'modern' as const, protocolVersion: '2026-01-26', server: undefined }), state: 'ready' as const }), + }), connection: Object.freeze({ capabilities: Object.freeze({}), protocolVersion: '2026-01-26', server: undefined }), state: 'ready' as const }), watchClosed: () => Object.freeze({ closed: false, unsubscribe: () => undefined }), }); }, diff --git a/packages/workbench/src/mcp/mcp-app-client.ts b/packages/workbench/src/mcp/mcp-app-client.ts index 2fdfb88c3..e9166065f 100644 --- a/packages/workbench/src/mcp/mcp-app-client.ts +++ b/packages/workbench/src/mcp/mcp-app-client.ts @@ -312,11 +312,6 @@ const runtimeStringOrUndefined = (value: unknown, label: string): string | undef return runtimeInvalid(`Runtime MCP App route returned an invalid ${label}.`); }; -const runtimeEra = (value: unknown): 'legacy' | 'modern' | undefined => { - if (value === undefined || value === 'legacy' || value === 'modern') return value; - return runtimeInvalid('Runtime MCP App route returned an invalid protocol era.'); -}; - const runtimeProfileId = (value: unknown): 'portable' | 'chatgpt' | 'claude' => { if (value === 'portable' || value === 'chatgpt' || value === 'claude') return value; return runtimeInvalid('Runtime MCP App route returned an invalid profile.'); @@ -389,19 +384,16 @@ const runtimeBinding = (value: unknown): McpAppRuntimeBindingSnapshot => { const runtimeConnection = (value: unknown): Readonly<{ readonly capabilities: Readonly> | undefined; - readonly protocolEra: 'legacy' | 'modern' | undefined; readonly protocolVersion: string | undefined; readonly server: Readonly<{ readonly name: string; readonly version: string }> | undefined; }> => { - const record = runtimeRecord(value, ['capabilities', 'protocolEra', 'protocolVersion', 'server']); + const record = runtimeRecord(value, ['capabilities', 'protocolVersion', 'server']); if (record.capabilities !== undefined && !isRecord(record.capabilities)) runtimeInvalid('Runtime MCP App route returned invalid server capabilities.'); const server = record.server === undefined ? undefined : runtimeRecord(record.server, ['name', 'version']); const capabilities = record.capabilities === undefined ? undefined : asRecord(record.capabilities); - const protocolEra = runtimeEra(record.protocolEra); const protocolVersion = runtimeStringOrUndefined(record.protocolVersion, 'protocol version'); return Object.freeze({ capabilities, - protocolEra, protocolVersion, server: server === undefined ? undefined : Object.freeze({ name: runtimeText(server.name, 'server name'), version: runtimeText(server.version, 'server version') }), }); diff --git a/packages/workbench/src/mcp/mcp-route-client.ts b/packages/workbench/src/mcp/mcp-route-client.ts index f7236b4b1..5c13adfd3 100644 --- a/packages/workbench/src/mcp/mcp-route-client.ts +++ b/packages/workbench/src/mcp/mcp-route-client.ts @@ -22,7 +22,6 @@ export interface McpRouteSessionBinding { export interface McpRouteConnection { readonly capabilities?: unknown; - readonly protocolEra?: 'legacy' | 'modern'; readonly protocolVersion?: string; readonly server?: Readonly<{ readonly name: string; readonly version: string }>; } @@ -220,15 +219,13 @@ const routeConnection = (value: unknown): McpRouteConnection => { serverSnapshot = Object.freeze({ name: server.name, version: server.version }); } if ( - !hasOnlyKeys(connection, ['capabilities', 'protocolEra', 'protocolVersion', 'server']) || - (connection.protocolEra !== undefined && connection.protocolEra !== 'legacy' && connection.protocolEra !== 'modern') || + !hasOnlyKeys(connection, ['capabilities', 'protocolVersion', 'server']) || (connection.protocolVersion !== undefined && typeof connection.protocolVersion !== 'string') ) { throw new McpRouteClientError('AB8019', 'Foreground MCP route returned an invalid connection.'); } return Object.freeze({ ...(connection.capabilities === undefined ? {} : { capabilities: connection.capabilities }), - ...(connection.protocolEra === undefined ? {} : { protocolEra: connection.protocolEra }), ...(connection.protocolVersion === undefined ? {} : { protocolVersion: connection.protocolVersion }), ...(serverSnapshot === undefined ? {} : { server: serverSnapshot }), }); diff --git a/packages/workbench/src/mcp/mcp-session-controller.ts b/packages/workbench/src/mcp/mcp-session-controller.ts index e41752310..b6cac9d45 100644 --- a/packages/workbench/src/mcp/mcp-session-controller.ts +++ b/packages/workbench/src/mcp/mcp-session-controller.ts @@ -294,7 +294,6 @@ const isRuntimeBinding = (value: unknown): value is DevRuntimeMcpAppRunBinding = const isRuntimeConnection = (value: unknown): value is McpRouteConnection => { if (!isRecord(value)) return false; - if (value.protocolEra !== undefined && value.protocolEra !== 'legacy' && value.protocolEra !== 'modern') return false; if (value.protocolVersion !== undefined && typeof value.protocolVersion !== 'string') return false; if (value.server !== undefined && (!isRecord(value.server) || typeof value.server.name !== 'string' || typeof value.server.version !== 'string')) return false; return true; diff --git a/packages/workbench/tests/agent-bundle-remote-transport.test.ts b/packages/workbench/tests/agent-bundle-remote-transport.test.ts index 9db1f1bbc..c18a0f947 100644 --- a/packages/workbench/tests/agent-bundle-remote-transport.test.ts +++ b/packages/workbench/tests/agent-bundle-remote-transport.test.ts @@ -63,7 +63,6 @@ const cancellableStream = (): Readonly<{ readonly cancelled: () => boolean; read const binding = Object.freeze({ epochId: 'epoch-a', serverName: 'weather', target: 'portable' }); const connection = Object.freeze({ capabilities: { tools: {} }, - protocolEra: 'modern', protocolVersion: '2025-11-25', server: { name: 'weather-fixture', version: '1.0.0' }, }); @@ -685,7 +684,7 @@ it('uses only exact runtime MCP routes and preserves the operation vector', asyn }, connection: { capabilities: { resources: { listChanged: true }, tools: { listChanged: true } }, - protocolEra: 'modern', protocolVersion: '2026-02-09', server: { name: 'weather-next', version: '2.0.0' }, + protocolVersion: '2026-02-09', server: { name: 'weather-next', version: '2.0.0' }, }, state: 'ready', }; diff --git a/packages/workbench/tests/mcp-app-client.test.ts b/packages/workbench/tests/mcp-app-client.test.ts index bf8e8123f..63a22c22b 100644 --- a/packages/workbench/tests/mcp-app-client.test.ts +++ b/packages/workbench/tests/mcp-app-client.test.ts @@ -146,7 +146,7 @@ const runtimePreview = Object.freeze({ definitionDigest: 'definition-a', registryRevision: 3, serverDigest: 'server-a', serverName: 'weather', sessionId: 'runtime-session-a', sessionRevision: 2, target: 'portable', transportDigest: 'transport-a', }), - connection: Object.freeze({ capabilities: Object.freeze({ tools: Object.freeze({}) }), protocolEra: 'modern', protocolVersion: '2026-01-26', server: Object.freeze({ name: 'weather', version: '1.0.0' }) }), + connection: Object.freeze({ capabilities: Object.freeze({ tools: Object.freeze({}) }), protocolVersion: '2026-01-26', server: Object.freeze({ name: 'weather', version: '1.0.0' }) }), state: 'ready', }), }); diff --git a/packages/workbench/tests/mcp-app-frame.test.ts b/packages/workbench/tests/mcp-app-frame.test.ts index 7d5b9c006..eada247c2 100644 --- a/packages/workbench/tests/mcp-app-frame.test.ts +++ b/packages/workbench/tests/mcp-app-frame.test.ts @@ -117,7 +117,7 @@ const mountedSecureRendererFixture = async () => { `const bootstrapUrl = ${JSON.stringify(bootstrapUrl)};`, "const policy = { allow: '', approvedPermissions: {}, revision: 1, warnings: [] };", "const metadata = { extensions: { claude: {}, openai: {} }, provenance: {}, raw: {}, standard: {} };", - "const preview = { binding: { definitionDigest: 'definition-a', evidence: 'simulated', id: 'runtime-binding', profileId: 'portable', profileVersion: 'agent-bundle:mcp-apps:2026-01-26', registryRevision: 3, runVector: { runtimeGenerationId: 'generation-a', sourceRevision: 'source-a', stateVersion: 1 }, serverDigest: 'server-a', serverName: 'weather', sessionId: 'runtime-session-a', sessionRevision: 2, target: 'portable', transportDigest: 'transport-a' }, clientSurface: { bootstrapUrl, origin: new URL(bootstrapUrl).origin }, documentPolicy: policy, kind: 'apps', metadata: { resource: metadata, result: metadata, tool: metadata }, operations: [], profile: { bootstrap: { kind: 'none' }, configExtensions: { entries: [], sourceRevision: 'source-a' }, descriptor: { claimsRealHostParity: false, evidence: 'simulated', id: 'portable', label: 'Portable MCP Apps', version: 'agent-bundle:mcp-apps:2026-01-26' }, hostContext: { availableDisplayModes: ['inline'], containerDimensions: { height: 720, width: 1024 }, deviceCapabilities: {}, displayMode: 'inline', locale: 'en-US', platform: 'web', safeAreaInsets: { bottom: 0, left: 0, right: 0, top: 0 }, styles: {}, theme: 'light', timeZone: 'UTC', toolInfo: {}, userAgent: 'agent-bundle-runtime-mcp-app/1' }, kind: 'apps', metadata, permissions: { camera: {}, geolocation: {} }, resourceUri: 'ui://weather/app.html', warnings: [] }, resource: { html: '
Weather
', permissions: { camera: {}, geolocation: {} } }, result: { appVisible: { content: [] }, isError: false, modelVisible: {} }, session: { binding: { definitionDigest: 'definition-a', registryRevision: 3, serverDigest: 'server-a', serverName: 'weather', sessionId: 'runtime-session-a', sessionRevision: 2, target: 'portable', transportDigest: 'transport-a' }, connection: { capabilities: { tools: {} }, protocolEra: 'modern', protocolVersion: '2026-01-26', server: { name: 'weather', version: '1.0.0' } }, state: 'ready' } };", + "const preview = { binding: { definitionDigest: 'definition-a', evidence: 'simulated', id: 'runtime-binding', profileId: 'portable', profileVersion: 'agent-bundle:mcp-apps:2026-01-26', registryRevision: 3, runVector: { runtimeGenerationId: 'generation-a', sourceRevision: 'source-a', stateVersion: 1 }, serverDigest: 'server-a', serverName: 'weather', sessionId: 'runtime-session-a', sessionRevision: 2, target: 'portable', transportDigest: 'transport-a' }, clientSurface: { bootstrapUrl, origin: new URL(bootstrapUrl).origin }, documentPolicy: policy, kind: 'apps', metadata: { resource: metadata, result: metadata, tool: metadata }, operations: [], profile: { bootstrap: { kind: 'none' }, configExtensions: { entries: [], sourceRevision: 'source-a' }, descriptor: { claimsRealHostParity: false, evidence: 'simulated', id: 'portable', label: 'Portable MCP Apps', version: 'agent-bundle:mcp-apps:2026-01-26' }, hostContext: { availableDisplayModes: ['inline'], containerDimensions: { height: 720, width: 1024 }, deviceCapabilities: {}, displayMode: 'inline', locale: 'en-US', platform: 'web', safeAreaInsets: { bottom: 0, left: 0, right: 0, top: 0 }, styles: {}, theme: 'light', timeZone: 'UTC', toolInfo: {}, userAgent: 'agent-bundle-runtime-mcp-app/1' }, kind: 'apps', metadata, permissions: { camera: {}, geolocation: {} }, resourceUri: 'ui://weather/app.html', warnings: [] }, resource: { html: '
Weather
', permissions: { camera: {}, geolocation: {} } }, result: { appVisible: { content: [] }, isError: false, modelVisible: {} }, session: { binding: { definitionDigest: 'definition-a', registryRevision: 3, serverDigest: 'server-a', serverName: 'weather', sessionId: 'runtime-session-a', sessionRevision: 2, target: 'portable', transportDigest: 'transport-a' }, connection: { capabilities: { tools: {} }, protocolVersion: '2026-01-26', server: { name: 'weather', version: '1.0.0' } }, state: 'ready' } };", "const response = (body) => new Response(JSON.stringify(body), { headers: { 'content-type': 'application/json' }, status: 200 });", "const foreground = new ForegroundRouteClient({ fetch: async (input, init) => { const path = new URL(String(input), location.origin).pathname; if (path === '/api/project/session') return response({ cookieName: 'agent-bundle-foreground-session-0123456789abcdef0123456789abcdef', instanceId: 'foreground-instance-a', origin: location.origin, token: 'foreground-secret' }); if (path === '/api/runtime/apps') return response({ preview }); if (path === '/api/runtime/apps/runtime-binding' && init?.method === 'DELETE') return response({ closed: true }); throw new Error('Unexpected runtime fixture request ' + path); } }); const runtime = new McpAppClient({ foreground });", "const trace = []; const iframeNodes = new Set();", diff --git a/packages/workbench/tests/mcp-app-preview-browser.test.ts b/packages/workbench/tests/mcp-app-preview-browser.test.ts index a006c5727..731b7d4b4 100644 --- a/packages/workbench/tests/mcp-app-preview-browser.test.ts +++ b/packages/workbench/tests/mcp-app-preview-browser.test.ts @@ -60,7 +60,7 @@ const mountedPreviewFixture = async () => { `const runtimeBootstrapUrl = ${JSON.stringify(bootstrapUrl)};`, "const response = (body) => new Response(JSON.stringify(body), { headers: { 'content-type': 'application/json' }, status: 200 });", "const initialRuntimePolicy = () => ({ allow: '', approvedPermissions: {}, revision: 1, warnings: [] });", - "const runtimePreviewFor = (suffix) => { const generation = 'generation-' + suffix; const source = 'source-' + suffix; const sessionId = 'runtime-session-' + suffix; const bindingId = 'runtime-binding-' + suffix; const binding = { definitionDigest: 'definition-' + suffix, evidence: 'simulated', id: bindingId, profileId: 'portable', profileVersion: 'agent-bundle:mcp-apps:2026-01-26', registryRevision: 3, runVector: { runtimeGenerationId: generation, sourceRevision: source, stateVersion: suffix === 'a' ? 1 : 2 }, serverDigest: 'server-' + suffix, serverName: 'weather', sessionId, sessionRevision: suffix === 'a' ? 2 : 3, target: 'weather', transportDigest: 'transport-' + suffix }; const stable = { definitionDigest: binding.definitionDigest, registryRevision: binding.registryRevision, serverDigest: binding.serverDigest, serverName: binding.serverName, sessionId: binding.sessionId, sessionRevision: binding.sessionRevision, target: binding.target, transportDigest: binding.transportDigest }; const metadata = { extensions: { claude: { nativeHooks: 'native-hooks-must-stay-hidden', opaque: 'opaque-value-must-stay-hidden', root: '/private/workspace/agent-bundle.config.ts' }, openai: { opaque: 'unregistered-openai-must-stay-hidden' } }, provenance: {}, raw: {}, standard: {} }; const configExtensions = { entries: [{ configured: true, id: 'extension:claude', key: 'claude', provenance: { kind: 'config', sourcePath: 'agent-bundle.config.ts' }, target: 'claude' }, { configured: true, id: 'extension:codex', key: 'codex', provenance: { kind: 'config', sourcePath: 'agent-bundle.config.ts' }, target: 'codex' }, { configured: true, id: 'extension:portable', key: 'portable', provenance: { kind: 'config', sourcePath: 'agent-bundle.config.ts' }, target: 'portable' }], sourceRevision: source }; return { binding, clientSurface: { bootstrapUrl: runtimeBootstrapUrl, origin: new URL(runtimeBootstrapUrl).origin }, documentPolicy: initialRuntimePolicy(), kind: 'apps', metadata: { resource: metadata, result: metadata, tool: metadata }, operations: [], profile: { bootstrap: { kind: 'none' }, configExtensions, descriptor: { claimsRealHostParity: false, evidence: 'simulated', id: 'portable', label: 'Portable MCP Apps', version: 'agent-bundle:mcp-apps:2026-01-26' }, hostContext: { availableDisplayModes: ['inline'], containerDimensions: { height: 720, width: 1024 }, deviceCapabilities: {}, displayMode: 'inline', locale: 'en-US', platform: 'web', safeAreaInsets: { bottom: 0, left: 0, right: 0, top: 0 }, styles: {}, theme: 'light', timeZone: 'UTC', toolInfo: {}, userAgent: 'agent-bundle-runtime-mcp-app/1' }, kind: 'apps', metadata, permissions: { camera: {} }, resourceUri: 'ui://weather/app.html', warnings: [] }, resource: { html: '
Weather
', permissions: { camera: {} } }, result: { appVisible: { content: [] }, isError: false, modelVisible: {} }, session: { binding: stable, connection: { capabilities: { tools: {} }, protocolEra: 'modern', protocolVersion: '2026-01-26', server: { name: 'weather', version: '1.0.0' } }, state: 'ready' } }; };", + "const runtimePreviewFor = (suffix) => { const generation = 'generation-' + suffix; const source = 'source-' + suffix; const sessionId = 'runtime-session-' + suffix; const bindingId = 'runtime-binding-' + suffix; const binding = { definitionDigest: 'definition-' + suffix, evidence: 'simulated', id: bindingId, profileId: 'portable', profileVersion: 'agent-bundle:mcp-apps:2026-01-26', registryRevision: 3, runVector: { runtimeGenerationId: generation, sourceRevision: source, stateVersion: suffix === 'a' ? 1 : 2 }, serverDigest: 'server-' + suffix, serverName: 'weather', sessionId, sessionRevision: suffix === 'a' ? 2 : 3, target: 'weather', transportDigest: 'transport-' + suffix }; const stable = { definitionDigest: binding.definitionDigest, registryRevision: binding.registryRevision, serverDigest: binding.serverDigest, serverName: binding.serverName, sessionId: binding.sessionId, sessionRevision: binding.sessionRevision, target: binding.target, transportDigest: binding.transportDigest }; const metadata = { extensions: { claude: { nativeHooks: 'native-hooks-must-stay-hidden', opaque: 'opaque-value-must-stay-hidden', root: '/private/workspace/agent-bundle.config.ts' }, openai: { opaque: 'unregistered-openai-must-stay-hidden' } }, provenance: {}, raw: {}, standard: {} }; const configExtensions = { entries: [{ configured: true, id: 'extension:claude', key: 'claude', provenance: { kind: 'config', sourcePath: 'agent-bundle.config.ts' }, target: 'claude' }, { configured: true, id: 'extension:codex', key: 'codex', provenance: { kind: 'config', sourcePath: 'agent-bundle.config.ts' }, target: 'codex' }, { configured: true, id: 'extension:portable', key: 'portable', provenance: { kind: 'config', sourcePath: 'agent-bundle.config.ts' }, target: 'portable' }], sourceRevision: source }; return { binding, clientSurface: { bootstrapUrl: runtimeBootstrapUrl, origin: new URL(runtimeBootstrapUrl).origin }, documentPolicy: initialRuntimePolicy(), kind: 'apps', metadata: { resource: metadata, result: metadata, tool: metadata }, operations: [], profile: { bootstrap: { kind: 'none' }, configExtensions, descriptor: { claimsRealHostParity: false, evidence: 'simulated', id: 'portable', label: 'Portable MCP Apps', version: 'agent-bundle:mcp-apps:2026-01-26' }, hostContext: { availableDisplayModes: ['inline'], containerDimensions: { height: 720, width: 1024 }, deviceCapabilities: {}, displayMode: 'inline', locale: 'en-US', platform: 'web', safeAreaInsets: { bottom: 0, left: 0, right: 0, top: 0 }, styles: {}, theme: 'light', timeZone: 'UTC', toolInfo: {}, userAgent: 'agent-bundle-runtime-mcp-app/1' }, kind: 'apps', metadata, permissions: { camera: {} }, resourceUri: 'ui://weather/app.html', warnings: [] }, resource: { html: '
Weather
', permissions: { camera: {} } }, result: { appVisible: { content: [] }, isError: false, modelVisible: {} }, session: { binding: stable, connection: { capabilities: { tools: {} }, protocolVersion: '2026-01-26', server: { name: 'weather', version: '1.0.0' } }, state: 'ready' } }; };", "let runtimePreview; let runtimeOperationTraces = []; let operationSequence = 0; let heldCreate; let bridgeCloseFailures = 0; let backendCloseFailures = 0; let unregisterThrows = 0; let currentHandle; let suspendedTransition; const runtimeEvents = []; const runtimeIframes = new Set(); const runtimeTrace = [];", "const originalSetAttribute = Element.prototype.setAttribute; Element.prototype.setAttribute = function(name, value) { if (this instanceof HTMLIFrameElement && ['allow', 'referrerpolicy', 'sandbox', 'src'].includes(name)) runtimeTrace.push({ name, value: String(value) }); return originalSetAttribute.call(this, name, value); };", "const observer = new MutationObserver((records) => { for (const record of records) for (const node of record.addedNodes) { if (node instanceof HTMLIFrameElement) runtimeIframes.add(node); if (node instanceof Element) node.querySelectorAll('iframe').forEach((frame) => runtimeIframes.add(frame)); } }); observer.observe(document.documentElement, { childList: true, subtree: true });", diff --git a/packages/workbench/tests/mcp-app-preview.test.ts b/packages/workbench/tests/mcp-app-preview.test.ts index 8ad24d050..dc958e443 100644 --- a/packages/workbench/tests/mcp-app-preview.test.ts +++ b/packages/workbench/tests/mcp-app-preview.test.ts @@ -162,7 +162,7 @@ const runtimePreview = Object.freeze({ result: Object.freeze({ appVisible: Object.freeze({}), isError: false, modelVisible: Object.freeze({}) }), session: Object.freeze({ binding: Object.freeze({ definitionDigest: 'definition-weather', registryRevision: 3, serverDigest: 'server-weather', serverName: 'weather', sessionId: 'runtime-session-weather', sessionRevision: 2, target: 'weather', transportDigest: 'transport-weather' }), - connection: Object.freeze({ capabilities: Object.freeze({ resources: Object.freeze({}), tools: Object.freeze({}) }), protocolEra: 'modern' as const, protocolVersion: '2026-01-26', server: Object.freeze({ name: 'weather', version: '1.0.0' }) }), + connection: Object.freeze({ capabilities: Object.freeze({ resources: Object.freeze({}), tools: Object.freeze({}) }), protocolVersion: '2026-01-26', server: Object.freeze({ name: 'weather', version: '1.0.0' }) }), state: 'ready' as const, }), }) as unknown as McpAppPreviewAppsSnapshot; diff --git a/packages/workbench/tests/mcp-page-app-browser.test.ts b/packages/workbench/tests/mcp-page-app-browser.test.ts index bd60a2eba..a46209887 100644 --- a/packages/workbench/tests/mcp-page-app-browser.test.ts +++ b/packages/workbench/tests/mcp-page-app-browser.test.ts @@ -133,7 +133,7 @@ const mountedPageFixture = async (mode: 'artifact' | 'runtime' | 'runtime-direct "const runtimeBinding = { definitionDigest: 'definition-runtime-weather', evidence: 'simulated', id: 'runtime-binding-weather', profileId: 'portable', profileVersion: 'agent-bundle:mcp-apps:2026-01-26', registryRevision: 4, runVector: { runtimeGenerationId: 'generation-runtime-weather', sourceRevision: 'source-runtime-weather', stateVersion: 1 }, serverDigest: 'server-runtime-weather', serverName: 'runtime-weather', sessionId: 'runtime-session-weather', sessionRevision: 2, target: 'portable', transportDigest: 'transport-runtime-weather' };", "const runtimeStableBinding = { definitionDigest: runtimeBinding.definitionDigest, registryRevision: runtimeBinding.registryRevision, serverDigest: runtimeBinding.serverDigest, serverName: runtimeBinding.serverName, sessionId: runtimeBinding.sessionId, sessionRevision: runtimeBinding.sessionRevision, target: runtimeBinding.target, transportDigest: runtimeBinding.transportDigest };", "const runtimeMetadata = { extensions: { claude: {}, openai: {} }, provenance: {}, raw: {}, standard: {} };", - "const runtimeResponseBinding = { ...runtimeStableBinding }; const runtimePreview = { binding: runtimeBinding, clientSurface: { bootstrapUrl: runtimeBootstrapUrl, origin: new URL(runtimeBootstrapUrl).origin }, documentPolicy: runtimePolicy, kind: 'apps', metadata: { resource: runtimeMetadata, result: runtimeMetadata, tool: runtimeMetadata }, operations: [], profile: { bootstrap: { kind: 'none' }, configExtensions: { entries: [], sourceRevision: 'source-runtime-weather' }, descriptor: { claimsRealHostParity: false, evidence: 'simulated', id: 'portable', label: 'Portable MCP Apps', version: 'agent-bundle:mcp-apps:2026-01-26' }, hostContext: { availableDisplayModes: ['inline'], containerDimensions: { height: 720, width: 1024 }, deviceCapabilities: {}, displayMode: 'inline', locale: 'en-US', platform: 'web', safeAreaInsets: { bottom: 0, left: 0, right: 0, top: 0 }, styles: {}, theme: 'light', timeZone: 'UTC', toolInfo: {}, userAgent: 'agent-bundle-runtime-mcp-app/1' }, kind: 'apps', metadata: runtimeMetadata, permissions: {}, resourceUri: 'ui://weather/runtime.html', warnings: [] }, resource: { html: '
Runtime weather
', permissions: {} }, result: { appVisible: { content: [] }, isError: false, modelVisible: {} }, session: { binding: runtimeResponseBinding, connection: { capabilities: { tools: {} }, protocolEra: 'modern', protocolVersion: '2026-01-26', server: { name: 'runtime-weather', version: '1.0.0' } }, state: 'ready' } };", + "const runtimeResponseBinding = { ...runtimeStableBinding }; const runtimePreview = { binding: runtimeBinding, clientSurface: { bootstrapUrl: runtimeBootstrapUrl, origin: new URL(runtimeBootstrapUrl).origin }, documentPolicy: runtimePolicy, kind: 'apps', metadata: { resource: runtimeMetadata, result: runtimeMetadata, tool: runtimeMetadata }, operations: [], profile: { bootstrap: { kind: 'none' }, configExtensions: { entries: [], sourceRevision: 'source-runtime-weather' }, descriptor: { claimsRealHostParity: false, evidence: 'simulated', id: 'portable', label: 'Portable MCP Apps', version: 'agent-bundle:mcp-apps:2026-01-26' }, hostContext: { availableDisplayModes: ['inline'], containerDimensions: { height: 720, width: 1024 }, deviceCapabilities: {}, displayMode: 'inline', locale: 'en-US', platform: 'web', safeAreaInsets: { bottom: 0, left: 0, right: 0, top: 0 }, styles: {}, theme: 'light', timeZone: 'UTC', toolInfo: {}, userAgent: 'agent-bundle-runtime-mcp-app/1' }, kind: 'apps', metadata: runtimeMetadata, permissions: {}, resourceUri: 'ui://weather/runtime.html', warnings: [] }, resource: { html: '
Runtime weather
', permissions: {} }, result: { appVisible: { content: [] }, isError: false, modelVisible: {} }, session: { binding: runtimeResponseBinding, connection: { capabilities: { tools: {} }, protocolVersion: '2026-01-26', server: { name: 'runtime-weather', version: '1.0.0' } }, state: 'ready' } };", "const runtimeRun = { completedAt: '2026-08-16T00:00:01.000Z', id: 'runtime-run-weather', input: { city: 'Paris' }, result: { app: { mcpBinding: runtimeStableBinding, resourceUri: 'ui://weather/runtime.html', surfaceId: 'mcp.edit-weather' }, modelVisible: { temperature: 22 }, trace: [], tree: [] }, startedAt: '2026-08-16T00:00:00.000Z', status: 'succeeded', surfaceId: 'mcp.render-weather', target: 'portable', vector: { runtimeGenerationId: 'generation-runtime-weather', sourceRevision: 'source-runtime-weather', stateVersion: 1 } };", "const runtimeProfile = { claimsRealHostParity: false, evidence: 'simulated', id: 'portable', label: 'Portable MCP Apps', version: 'agent-bundle:mcp-apps:2026-01-26' }; const runtimeSurface = { fixtures: [], id: 'mcp.render-weather', kind: 'mcp-app', label: 'Runtime weather', readOnly: false, targets: ['portable'] };", "const runtimeEvents = []; let heldCreate = deferred(); let bridgeCloseFailures = 0; let backendCloseFailures = 0; let registeredPreviewClose; const foreground = new ForegroundRouteClient({ fetch: async (input, init) => { const path = new URL(String(input), location.origin).pathname; if (path === '/api/project/session') { runtimeEvents.push('bootstrap'); return response({ cookieName: 'agent-bundle-foreground-session-0123456789abcdef0123456789abcdef', instanceId: 'foreground-instance-a', origin: location.origin, token: 'foreground-secret' }); } if (path === '/api/runtime/apps' && init?.method === 'POST') { const request = JSON.parse(String(init?.body)); runtimeEvents.push('create:' + request.runId + ':' + request.profileId + ':' + request.expectedGenerationId); return heldCreate.promise; } if (path.startsWith('/api/runtime/apps/') && init?.method === 'DELETE') { const bindingId = decodeURIComponent(path.slice('/api/runtime/apps/'.length)); runtimeEvents.push('backend:' + bindingId); if (backendCloseFailures > 0) { backendCloseFailures -= 1; runtimeEvents.push('backend-failed:' + bindingId); throw new Error('runtime backend close failed'); } return response({ closed: true }); } throw new Error('Unexpected runtime fixture request ' + path); } }); const runtime = new McpAppClient({ foreground });", diff --git a/packages/workbench/tests/mcp-session-controller.test.ts b/packages/workbench/tests/mcp-session-controller.test.ts index 642f5dfbe..d4b26906a 100644 --- a/packages/workbench/tests/mcp-session-controller.test.ts +++ b/packages/workbench/tests/mcp-session-controller.test.ts @@ -15,7 +15,6 @@ import { McpRouteClientError, type McpRouteCatalog } from '../src/mcp/mcp-route- const binding = Object.freeze({ epochId: 'epoch-a', serverName: 'weather', target: 'portable' as const }); const connection = Object.freeze({ capabilities: { tools: {} }, - protocolEra: 'modern' as const, protocolVersion: '2025-11-25', server: { name: 'weather-fixture', version: '1.0.0' }, }); @@ -27,7 +26,6 @@ const runtimeSession = Object.freeze({ }), connection: Object.freeze({ capabilities: Object.freeze({ resources: Object.freeze({ listChanged: true }), tools: Object.freeze({ listChanged: true }) }), - protocolEra: 'modern' as const, protocolVersion: '2026-07-28', server: Object.freeze({ name: 'weather-runtime', version: '4.2.0' }), }), @@ -67,7 +65,6 @@ const restartedRuntimeSession = Object.freeze({ }), connection: Object.freeze({ capabilities: Object.freeze({ resources: Object.freeze({ listChanged: true }), tools: Object.freeze({ listChanged: true }) }), - protocolEra: 'modern' as const, protocolVersion: '2026-02-09', server: Object.freeze({ name: 'weather-runtime-next', version: '5.0.0' }), }), @@ -2071,7 +2068,6 @@ it('preserves binding, trace, and history through restart while refreshing conne let catalogCount = 0; const restarted = Object.freeze({ capabilities: { prompts: {} }, - protocolEra: 'modern' as const, protocolVersion: '2026-01-26', server: { name: 'weather-fixture', version: '2.0.0' }, });