Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/remove-protocol-era.md
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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 }),
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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;
}
Expand Down
1 change: 0 additions & 1 deletion packages/agent-bundle/src/dev/mcp-session/mcp-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
});
Expand Down
1 change: 0 additions & 1 deletion packages/agent-bundle/src/dev/runtime-mcp-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down
1 change: 0 additions & 1 deletion packages/agent-bundle/src/dev/runtime-mcp-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }),
}),
Expand Down
1 change: 0 additions & 1 deletion packages/agent-bundle/src/dev/runtime-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
8 changes: 4 additions & 4 deletions packages/agent-bundle/tests/dev-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
};
},
Expand Down Expand Up @@ -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',
},
});
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' }),
}),
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-bundle/tests/mcp-session-routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const startRoutes = async (service: McpSessionRouteService): Promise<StartedRout

class RecordingSession implements McpSessionRouteSession {
readonly binding = Object.freeze({ epochId: 'epoch-a', serverName: 'weather', target: 'portable' });
readonly connection = Object.freeze({ capabilities: { tools: {} }, protocolEra: 'modern' as const, protocolVersion: '2025-11-25', server: { name: 'fixture', version: '1.0.0' } });
readonly connection = Object.freeze({ capabilities: { tools: {} }, protocolVersion: '2025-11-25', server: { name: 'fixture', version: '1.0.0' } });
readonly id = 'session-a';
readonly timeoutMs = 5_000;
readonly calls: unknown[] = [];
Expand Down
2 changes: 0 additions & 2 deletions packages/agent-bundle/tests/mcp-session-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1673,7 +1673,6 @@ it('exposes one opaque, epoch-bound session handle with a bounded ordered wire t
transport.onmessage?.(progressFrame);
},
getNegotiatedProtocolVersion: () => '2026-07-28',
getProtocolEra: () => 'modern' as const,
getPrompt: async () => ({ messages: [] }),
getServerCapabilities: () => ({ logging: {} }),
getServerVersion: () => ({ name: 'wire-fixture', version: '1.0.0' }),
Expand All @@ -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' },
});
Expand Down
1 change: 0 additions & 1 deletion packages/agent-bundle/tests/runtime-mcp-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ const deferred = <T = void>(): 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' }),
});
Expand Down
8 changes: 3 additions & 5 deletions packages/agent-bundle/tests/runtime-mcp-routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' }),
}),
Expand All @@ -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' }),
}),
Expand Down Expand Up @@ -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');

Expand All @@ -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',
},
});
Expand All @@ -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 }),
});
},
Expand Down
10 changes: 1 addition & 9 deletions packages/workbench/src/mcp/mcp-app-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
Expand Down Expand Up @@ -389,19 +384,16 @@ const runtimeBinding = (value: unknown): McpAppRuntimeBindingSnapshot => {

const runtimeConnection = (value: unknown): Readonly<{
readonly capabilities: Readonly<Record<string, McpAppJsonValue>> | 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') }),
});
Expand Down
5 changes: 1 addition & 4 deletions packages/workbench/src/mcp/mcp-route-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }>;
}
Expand Down Expand Up @@ -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 }),
});
Expand Down
1 change: 0 additions & 1 deletion packages/workbench/src/mcp/mcp-session-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
});
Expand Down Expand Up @@ -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',
};
Expand Down
2 changes: 1 addition & 1 deletion packages/workbench/tests/mcp-app-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
}),
});
Expand Down
Loading
Loading