From 5684d1742a37464c1f02c498eef8c4749c0b0260 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Tue, 28 Jul 2026 23:27:25 +0200 Subject: [PATCH] fix(v2): enforce method contracts --- src/v2/acp.test.ts | 225 ++++++++++++++++++++++-- src/v2/acp.ts | 413 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 563 insertions(+), 75 deletions(-) diff --git a/src/v2/acp.test.ts b/src/v2/acp.test.ts index 4ec569d..7da387d 100644 --- a/src/v2/acp.test.ts +++ b/src/v2/acp.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { describe, expect, expectTypeOf, it } from "vitest"; import { PROTOCOL_VERSION, @@ -19,10 +19,13 @@ import { import type { AgentContext, Annotations, + ClientContext, DiffPatch, + ExtensionMethod, InitializeResponse, McpServer, NewSessionRequest, + NewSessionResponse, SessionInfo, SessionInfoUpdate, SessionUpdate, @@ -31,6 +34,94 @@ import type { const clientInfo = { name: "test-client", version: "1.0.0" }; const agentInfo = { name: "test-agent", version: "1.0.0" }; +function assertV2MethodTypes( + agentContext: ClientContext, + clientContext: AgentContext, +): void { + // @ts-expect-error Built-in methods must not fall through the extension overload. + agentContext.request(methods.agent.session.new, { sessionId: "wrong" }); + // @ts-expect-error Built-in notifications must not fall through the extension overload. + agentContext.notify(methods.agent.session.cancel, {}); + agent().onRequest( + // @ts-expect-error Built-in handlers cannot replace their generated params parser. + methods.agent.session.new, + (params: unknown) => params, + () => ({ sessionId: "wrong-parser" }), + ); + + const outputs = agentContext.batch([ + batchRequest(methods.agent.session.new, { + cwd: "/workspace", + mcpServers: [], + }), + batchNotification(methods.agent.session.cancel, { + sessionId: "session-1", + }), + ] as const); + expectTypeOf(outputs).toEqualTypeOf>(); + void agentContext.notify(methods.protocol.cancelRequest, { requestId: 1 }); + + const clientRequest = batchRequest(methods.client.mcp.disconnect, { + connectionId: "connection-1", + }); + // @ts-expect-error Client-directed methods cannot be sent in an agent-directed batch. + agentContext.batch([clientRequest] as const); + agentContext.batch([ + { + kind: "request", + method: methods.agent.session.new, + // @ts-expect-error Raw built-in batch entries must use method-specific params. + params: { sessionId: "wrong" }, + }, + ] as const); + + clientContext.batch([clientRequest] as const); +} + +void assertV2MethodTypes; + +function memoryWireStreamPair(): [sdk.Stream, sdk.Stream] { + const leftToRight = new TransformStream(); + const rightToLeft = new TransformStream(); + return [ + { + readable: rightToLeft.readable, + writable: leftToRight.writable, + }, + { + readable: leftToRight.readable, + writable: rightToLeft.writable, + }, + ]; +} + +async function respondToNextRequest( + stream: sdk.Stream, + result: unknown, +): Promise { + const reader = stream.readable.getReader(); + const request = await reader.read(); + reader.releaseLock(); + if ( + request.done || + Array.isArray(request.value) || + !("id" in request.value) + ) { + throw new Error("Expected one JSON-RPC request"); + } + + const writer = stream.writable.getWriter(); + try { + await writer.write({ + jsonrpc: "2.0", + id: request.value.id, + result, + }); + } finally { + writer.releaseLock(); + } +} + describe("experimental v2 date-time schemas", () => { it("preserves RFC 3339 timestamps with timezone offsets as strings", () => { const timestamp = "2026-07-20T01:00:00+01:00"; @@ -583,22 +674,119 @@ describe("experimental v2 app API", () => { ).rejects.toMatchObject({ code: -32600 }); }); + it("validates every built-in direct response before returning it", async () => { + const [clientStream, peerStream] = memoryWireStreamPair(); + const response = client().connectWith(clientStream, (agentContext) => + agentContext.request(methods.agent.session.new, { + cwd: "/workspace", + mcpServers: [], + }), + ); + + await respondToNextRequest(peerStream, { sessionId: 42 }); + await expect(response).rejects.toThrow(); + }); + + it("validates built-in batch responses before applying caller mappings", async () => { + const [clientStream, peerStream] = memoryWireStreamPair(); + let mapped = false; + const response = client().connectWith(clientStream, (agentContext) => + agentContext.batch([ + batchRequest( + methods.agent.session.new, + { cwd: "/workspace", mcpServers: [] }, + (session) => { + mapped = true; + return session.sessionId; + }, + ), + ] as const), + ); + + const reader = peerStream.readable.getReader(); + const request = await reader.read(); + reader.releaseLock(); + if ( + request.done || + !Array.isArray(request.value) || + request.value.length !== 1 || + !("id" in request.value[0]) + ) { + throw new Error("Expected one JSON-RPC batch request"); + } + const writer = peerStream.writable.getWriter(); + try { + await writer.write([ + { + jsonrpc: "2.0", + id: request.value[0].id, + result: { sessionId: 42 }, + }, + ]); + } finally { + writer.releaseLock(); + } + + await expect(response).rejects.toThrow(); + expect(mapped).toBe(false); + }); + + it("rejects peer null for empty responses but preserves local void handlers", async () => { + const [clientStream, peerStream] = memoryWireStreamPair(); + const invalidResponse = client().connectWith(clientStream, (agentContext) => + agentContext.request(methods.agent.session.delete, { + sessionId: "session-1", + }), + ); + + await respondToNextRequest(peerStream, null); + await expect(invalidResponse).rejects.toThrow(); + + await expect( + client().connectWith( + agent().onRequest(methods.agent.session.delete, () => {}), + (agentContext) => + agentContext.request(methods.agent.session.delete, { + sessionId: "session-1", + }), + ), + ).resolves.toEqual({}); + }); + it("requires and preserves underscore-prefixed extension methods", async () => { const parseValue = (params: unknown): { value: string } => params as { value: string }; const returnValue = ({ params }: { params: { value: string } }) => params; + const uncheckedExtension = (method: string): ExtensionMethod => + method as ExtensionMethod; expect(() => - agent().onRequest("vendor/echo", parseValue, returnValue), + agent().onRequest( + uncheckedExtension("vendor/echo"), + parseValue, + returnValue, + ), ).toThrow("must start with '_'"); expect(() => - agent().onNotification("vendor/event", parseValue, () => {}), + agent().onNotification( + uncheckedExtension("vendor/event"), + parseValue, + () => {}, + ), ).toThrow("must start with '_'"); expect(() => - client().onRequest("vendor/echo", parseValue, returnValue), + client().onRequest( + uncheckedExtension("vendor/echo"), + parseValue, + returnValue, + ), ).toThrow("must start with '_'"); expect(() => - client().onNotification("vendor/event", parseValue, () => {}), + client().onNotification( + uncheckedExtension("vendor/event"), + parseValue, + () => {}, + ), ).toThrow("must start with '_'"); let notificationValue: string | undefined; @@ -615,10 +803,16 @@ describe("experimental v2 app API", () => { methods.agent.initialize, async ({ client: clientContext }) => { expect(() => - clientContext.request("vendor/client-request", {}), + clientContext.request( + uncheckedExtension("vendor/client-request"), + {}, + ), ).toThrow("must start with '_'"); expect(() => - clientContext.notify("vendor/client-notification", {}), + clientContext.notify( + uncheckedExtension("vendor/client-notification"), + {}, + ), ).toThrow("must start with '_'"); await clientContext.notify("_vendor/acme/event", { value: "notification", @@ -628,15 +822,18 @@ describe("experimental v2 app API", () => { ); await clientApp.connectWith(agentApp, async (agentContext) => { - expect(() => agentContext.request("vendor/request", {})).toThrow( - "must start with '_'", - ); - expect(() => agentContext.notify("vendor/notification", {})).toThrow( - "must start with '_'", - ); + expect(() => + agentContext.request(uncheckedExtension("vendor/request"), {}), + ).toThrow("must start with '_'"); + expect(() => + agentContext.notify(uncheckedExtension("vendor/notification"), {}), + ).toThrow("must start with '_'"); expect(() => agentContext.batch([ - batchNotification("vendor/batch-notification", {}), + batchNotification( + uncheckedExtension("vendor/batch-notification"), + {}, + ), ] as const), ).toThrow("must start with '_'"); diff --git a/src/v2/acp.ts b/src/v2/acp.ts index 1e65336..7a924cc 100644 --- a/src/v2/acp.ts +++ b/src/v2/acp.ts @@ -85,7 +85,7 @@ export function ndJsonStream( return createJsonStream(output, input); } -export { RequestError, batchNotification, batchRequest } from "../jsonrpc.js"; +export { RequestError } from "../jsonrpc.js"; export { AgentProtocolRouter, agentProtocolRouter, @@ -117,6 +117,8 @@ export type { } from "../jsonrpc.js"; import { + batchNotification as jsonRpcBatchNotification, + batchRequest as jsonRpcBatchRequest, Connection, Handled, HandlerRegistration, @@ -125,7 +127,9 @@ import { import type { AnyWireMessage, BatchEntry, + BatchNotification, BatchOutputs, + BatchRequest, ConnectionBuilder, ConnectionContext, HandleResult, @@ -136,6 +140,136 @@ import type { SendRequestOptions, } from "../jsonrpc.js"; +/** + * ACP v2 extension method name. + * + * Custom methods must begin with `_` so they cannot collide with present or + * future protocol methods. + * + * @experimental + */ +export type ExtensionMethod = `_${string}`; + +/** + * Creates a typed request descriptor for an ACP v2 batch. + * + * Built-in method literals infer their params and response types. Extension + * methods retain the low-level helper's explicit params/response generics. + * + * @experimental + */ +export function batchRequest( + method: Method, + params: AgentRequestParamsByMethod[Method], + options?: SendRequestOptions, +): BatchRequest< + AgentRequestParamsByMethod[Method], + AgentRequestResponsesByMethod[Method] +> & { + readonly method: Method; +}; +export function batchRequest( + method: Method, + params: AgentRequestParamsByMethod[Method], + mapResponse: (response: AgentRequestResponsesByMethod[Method]) => Output, + options?: SendRequestOptions, +): BatchRequest< + AgentRequestParamsByMethod[Method], + AgentRequestResponsesByMethod[Method], + Output +> & { + readonly method: Method; +}; +export function batchRequest( + method: Method, + params: ClientRequestParamsByMethod[Method], + options?: SendRequestOptions, +): BatchRequest< + ClientRequestParamsByMethod[Method], + ClientRequestResponsesByMethod[Method] +> & { + readonly method: Method; +}; +export function batchRequest( + method: Method, + params: ClientRequestParamsByMethod[Method], + mapResponse: (response: ClientRequestResponsesByMethod[Method]) => Output, + options?: SendRequestOptions, +): BatchRequest< + ClientRequestParamsByMethod[Method], + ClientRequestResponsesByMethod[Method], + Output +> & { + readonly method: Method; +}; +export function batchRequest( + method: ExtensionMethod, + params?: Params, + options?: SendRequestOptions, +): BatchRequest & { + readonly method: ExtensionMethod; +}; +export function batchRequest( + method: ExtensionMethod, + params: Params | undefined, + mapResponse: (response: Response) => Output, + options?: SendRequestOptions, +): BatchRequest & { + readonly method: ExtensionMethod; +}; +export function batchRequest( + method: string, + params?: Params, + mapResponseOrOptions?: ((response: Response) => unknown) | SendRequestOptions, + options?: SendRequestOptions, +): BatchRequest & { + readonly method: string; +} { + const request = + typeof mapResponseOrOptions === "function" + ? jsonRpcBatchRequest(method, params, mapResponseOrOptions, options) + : jsonRpcBatchRequest(method, params, mapResponseOrOptions); + return request; +} + +/** + * Creates a typed notification descriptor for an ACP v2 batch. + * + * @experimental + */ +export function batchNotification( + method: Method, + params: AgentNotificationParamsByMethod[Method], +): BatchNotification & { + readonly method: Method; +}; +export function batchNotification( + method: Method, + params: ClientNotificationParamsByMethod[Method], +): BatchNotification & { + readonly method: Method; +}; +export function batchNotification( + method: typeof schema.PROTOCOL_METHODS.cancel_request, + params: schema.CancelRequestNotification, +): BatchNotification & { + readonly method: typeof schema.PROTOCOL_METHODS.cancel_request; +}; +export function batchNotification( + method: ExtensionMethod, + params?: Params, +): BatchNotification & { + readonly method: ExtensionMethod; +}; +export function batchNotification( + method: string, + params?: Params, +): BatchNotification & { + readonly method: string; +} { + return jsonRpcBatchNotification(method, params); +} + function emptyObjectResponse(response: T | null | undefined | void): T { return response ?? ({} as T); } @@ -202,9 +336,7 @@ function normalizeOutgoingV2InitializeRequest( }); } -function mapV2InitializeResponse( - response: schema.InitializeResponse, -): schema.InitializeResponse { +function mapV2InitializeResponse(response: unknown): schema.InitializeResponse { const parsed = validate.zInitializeResponse.parse(response); if (parsed.protocolVersion !== schema.PROTOCOL_VERSION) { throw RequestError.invalidRequest( @@ -218,26 +350,41 @@ function mapV2InitializeResponse( return parsed; } -function normalizeClientBatch( +function parseRequestResponse( + spec: { response?: ParamsParser }, + response: unknown, +): unknown { + return parseParams(spec.response, response); +} + +function normalizeV2Batch( entries: Entries & { readonly 0: BatchEntry }, + requestSpecs: Record< + string, + { response?: ParamsParser } | undefined + >, + normalizeInitialize = false, ): Entries & { readonly 0: BatchEntry } { return entries.map((entry) => { - if ( - entry.kind !== "request" || - entry.method !== schema.AGENT_METHODS.initialize - ) { + if (entry.kind !== "request") { return entry; } + const spec = requestSpecs[entry.method]; const mapResponse = entry.mapResponse as - ((response: schema.InitializeResponse) => unknown) | undefined; + ((response: unknown) => unknown) | undefined; return { ...entry, - params: normalizeOutgoingV2InitializeRequest(entry.params), - mapResponse: (response: schema.InitializeResponse) => { - const parsed = mapV2InitializeResponse(response); - return mapResponse ? mapResponse(parsed) : parsed; - }, + params: + normalizeInitialize && entry.method === schema.AGENT_METHODS.initialize + ? normalizeOutgoingV2InitializeRequest(entry.params) + : entry.params, + mapResponse: spec + ? (response: unknown) => { + const parsed = parseRequestResponse(spec, response); + return mapResponse ? mapResponse(parsed) : parsed; + } + : mapResponse, }; }) as unknown as Entries & { readonly 0: BatchEntry }; } @@ -396,6 +543,70 @@ export interface ClientConnection extends AcpConnection { readonly agent: ClientContext; } +/** + * One batch entry sent to an ACP v2 agent. + * + * @experimental + */ +export type AgentBatchEntry = + | { + [Method in AgentRequestMethod]: BatchRequest< + AgentRequestParamsByMethod[Method], + AgentRequestResponsesByMethod[Method], + unknown + > & { + readonly method: Method; + }; + }[AgentRequestMethod] + | { + [Method in AgentNotificationMethod]: BatchNotification< + AgentNotificationParamsByMethod[Method] + > & { + readonly method: Method; + }; + }[AgentNotificationMethod] + | (BatchNotification & { + readonly method: typeof schema.PROTOCOL_METHODS.cancel_request; + }) + | (BatchRequest & { + readonly method: ExtensionMethod; + }) + | (BatchNotification & { + readonly method: ExtensionMethod; + }); + +/** + * One batch entry sent to an ACP v2 client. + * + * @experimental + */ +export type ClientBatchEntry = + | { + [Method in ClientRequestMethod]: BatchRequest< + ClientRequestParamsByMethod[Method], + ClientRequestResponsesByMethod[Method], + unknown + > & { + readonly method: Method; + }; + }[ClientRequestMethod] + | { + [Method in ClientNotificationMethod]: BatchNotification< + ClientNotificationParamsByMethod[Method] + > & { + readonly method: Method; + }; + }[ClientNotificationMethod] + | (BatchNotification & { + readonly method: typeof schema.PROTOCOL_METHODS.cancel_request; + }) + | (BatchRequest & { + readonly method: ExtensionMethod; + }) + | (BatchNotification & { + readonly method: ExtensionMethod; + }); + class AcpContext { /** @internal */ constructor( @@ -476,7 +687,7 @@ export class AgentContext extends AcpContext { options?: SendRequestOptions, ): Promise; request( - method: string, + method: ExtensionMethod, params?: Params, options?: SendRequestOptions, ): Promise; @@ -488,7 +699,12 @@ export class AgentContext extends AcpContext { assertV2Method(method, clientRequestSpecsByMethod, "request"); const spec = clientRequestSpecsByMethod[method] as AcpRequestSpec | undefined; - return this.sendRequest(method, params, spec?.mapResponse, options); + return this.sendRequest( + method, + params, + spec ? (response) => parseRequestResponse(spec, response) : undefined, + options, + ); } /** @@ -501,7 +717,14 @@ export class AgentContext extends AcpContext { method: Method, params: ClientNotificationParamsByMethod[Method], ): Promise; - notify(method: string, params?: Params): Promise; + notify( + method: typeof schema.PROTOCOL_METHODS.cancel_request, + params: schema.CancelRequestNotification, + ): Promise; + notify( + method: ExtensionMethod, + params?: Params, + ): Promise; notify(method: string, params?: unknown): Promise { assertV2Method( method, @@ -515,15 +738,20 @@ export class AgentContext extends AcpContext { /** * Sends requests and notifications to the client as one JSON-RPC batch. */ - batch( - entries: Entries & { readonly 0: BatchEntry }, + batch( + entries: Entries & { readonly 0: ClientBatchEntry }, ): Promise> { assertV2BatchMethods( entries, clientRequestSpecsByMethod, clientNotificationSpecsByMethod, ); - return this.sendBatch(entries); + return this.sendBatch( + normalizeV2Batch( + entries as Entries & { readonly 0: BatchEntry }, + clientRequestSpecsByMethod, + ), + ); } } @@ -551,15 +779,8 @@ export class ClientContext extends AcpContext { params: schema.NewSessionRequest, options?: SendRequestOptions, ): Promise { - return this.sendRequest< - schema.NewSessionRequest, - schema.NewSessionResponse, - ActiveSession - >( - schema.AGENT_METHODS.session_new, - params, + return this.request(schema.AGENT_METHODS.session_new, params, options).then( (response) => this.attachSession(response), - options, ); } @@ -655,7 +876,7 @@ export class ClientContext extends AcpContext { options?: SendRequestOptions, ): Promise; request( - method: string, + method: ExtensionMethod, params?: Params, options?: SendRequestOptions, ): Promise; @@ -671,7 +892,12 @@ export class ClientContext extends AcpContext { method === schema.AGENT_METHODS.initialize ? normalizeOutgoingV2InitializeRequest(params) : params; - return this.sendRequest(method, wireParams, spec?.mapResponse, options); + return this.sendRequest( + method, + wireParams, + spec ? (response) => parseRequestResponse(spec, response) : undefined, + options, + ); } /** @@ -684,7 +910,14 @@ export class ClientContext extends AcpContext { method: Method, params: AgentNotificationParamsByMethod[Method], ): Promise; - notify(method: string, params?: Params): Promise; + notify( + method: typeof schema.PROTOCOL_METHODS.cancel_request, + params: schema.CancelRequestNotification, + ): Promise; + notify( + method: ExtensionMethod, + params?: Params, + ): Promise; notify(method: string, params?: unknown): Promise { assertV2Method( method, @@ -698,15 +931,21 @@ export class ClientContext extends AcpContext { /** * Sends requests and notifications to the agent as one JSON-RPC batch. */ - batch( - entries: Entries & { readonly 0: BatchEntry }, + batch( + entries: Entries & { readonly 0: AgentBatchEntry }, ): Promise> { assertV2BatchMethods( entries, agentRequestSpecsByMethod, agentNotificationSpecsByMethod, ); - return this.sendBatch(normalizeClientBatch(entries)); + return this.sendBatch( + normalizeV2Batch( + entries as Entries & { readonly 0: BatchEntry }, + agentRequestSpecsByMethod, + true, + ), + ); } } @@ -1434,10 +1673,11 @@ function parseParams( return parser.parse(params); } -type AcpRequestSpec = { +type AcpRequestSpec = { method: string; params?: ParamsParser; - mapResponse?: (response: Response) => WireResponse; + response?: ParamsParser; + serializeResponse?: (response: HandlerResponse) => Response; }; type AcpNotificationSpec = { @@ -1445,12 +1685,13 @@ type AcpNotificationSpec = { params?: ParamsParser; }; -function requestSpec( +function requestSpec( method: string, params: ParamsParser, - mapResponse?: (response: Response) => WireResponse, -): AcpRequestSpec { - return { method, params, mapResponse }; + response: ParamsParser, + serializeResponse?: (response: HandlerResponse) => Response, +): AcpRequestSpec { + return { method, params, response, serializeResponse }; } function notificationSpec( @@ -1460,18 +1701,18 @@ function notificationSpec( return { method, params }; } -function registerAppRequest( +function registerAppRequest( builder: ConnectionBuilder, - spec: AcpRequestSpec, + spec: AcpRequestSpec, context: ( params: Params, cx: ConnectionContext, signal: AbortSignal, requestId: JsonRpcId, ) => Context, - handler: (context: Context) => MaybePromise, + handler: (context: Context) => MaybePromise, ): void { - builder.onReceiveRequest( + builder.onReceiveRequest( spec.method, (params) => parseParams(spec.params, params), async (params, responder, cx) => { @@ -1479,9 +1720,9 @@ function registerAppRequest( context(params, cx, responder.signal, responder.id), ); await responder.respond( - (spec.mapResponse - ? spec.mapResponse(response) - : response) as WireResponse, + spec.serializeResponse + ? spec.serializeResponse(response) + : (response as unknown as Response), ); }, ); @@ -1519,6 +1760,7 @@ const agentRequestSpecs = { schema.AGENT_METHODS.initialize, parseV2InitializeRequest, mapV2InitializeResponse, + mapV2InitializeResponse, ), loginAuth: requestSpec< schema.LoginAuthRequest, @@ -1527,12 +1769,17 @@ const agentRequestSpecs = { >( schema.AGENT_METHODS.auth_login, validate.zLoginAuthRequest, + validate.zLoginAuthResponse, emptyObjectResponse, ), unstable_listProviders: requestSpec< schema.ListProvidersRequest, schema.ListProvidersResponse - >(schema.AGENT_METHODS.providers_list, validate.zListProvidersRequest), + >( + schema.AGENT_METHODS.providers_list, + validate.zListProvidersRequest, + validate.zListProvidersResponse, + ), unstable_setProvider: requestSpec< schema.SetProviderRequest, schema.SetProviderResponse | void, @@ -1540,6 +1787,7 @@ const agentRequestSpecs = { >( schema.AGENT_METHODS.providers_set, validate.zSetProviderRequest, + validate.zSetProviderResponse, emptyObjectResponse, ), unstable_disableProvider: requestSpec< @@ -1549,11 +1797,13 @@ const agentRequestSpecs = { >( schema.AGENT_METHODS.providers_disable, validate.zDisableProviderRequest, + validate.zDisableProviderResponse, emptyObjectResponse, ), newSession: requestSpec( schema.AGENT_METHODS.session_new, validate.zNewSessionRequest, + validate.zNewSessionResponse, ), setSessionConfigOption: requestSpec< schema.SetSessionConfigOptionRequest, @@ -1561,6 +1811,7 @@ const agentRequestSpecs = { >( schema.AGENT_METHODS.session_set_config_option, validate.zSetSessionConfigOptionRequest, + validate.zSetSessionConfigOptionResponse, ), prompt: requestSpec< schema.PromptRequest, @@ -1569,16 +1820,25 @@ const agentRequestSpecs = { >( schema.AGENT_METHODS.session_prompt, validate.zPromptRequest, + validate.zPromptResponse, emptyObjectResponse, ), unstable_messageMcp: requestSpec< schema.MessageMcpRequest, schema.MessageMcpResponse - >(schema.AGENT_METHODS.mcp_message, validate.zMessageMcpRequest), + >( + schema.AGENT_METHODS.mcp_message, + validate.zMessageMcpRequest, + validate.zMessageMcpResponse, + ), listSessions: requestSpec< schema.ListSessionsRequest, schema.ListSessionsResponse - >(schema.AGENT_METHODS.session_list, validate.zListSessionsRequest), + >( + schema.AGENT_METHODS.session_list, + validate.zListSessionsRequest, + validate.zListSessionsResponse, + ), deleteSession: requestSpec< schema.DeleteSessionRequest, schema.DeleteSessionResponse | void, @@ -1586,16 +1846,25 @@ const agentRequestSpecs = { >( schema.AGENT_METHODS.session_delete, validate.zDeleteSessionRequest, + validate.zDeleteSessionResponse, emptyObjectResponse, ), unstable_forkSession: requestSpec< schema.ForkSessionRequest, schema.ForkSessionResponse - >(schema.AGENT_METHODS.session_fork, validate.zForkSessionRequest), + >( + schema.AGENT_METHODS.session_fork, + validate.zForkSessionRequest, + validate.zForkSessionResponse, + ), resumeSession: requestSpec< schema.ResumeSessionRequest, schema.ResumeSessionResponse - >(schema.AGENT_METHODS.session_resume, validate.zResumeSessionRequest), + >( + schema.AGENT_METHODS.session_resume, + validate.zResumeSessionRequest, + validate.zResumeSessionResponse, + ), closeSession: requestSpec< schema.CloseSessionRequest, schema.CloseSessionResponse | void, @@ -1603,6 +1872,7 @@ const agentRequestSpecs = { >( schema.AGENT_METHODS.session_close, validate.zCloseSessionRequest, + validate.zCloseSessionResponse, emptyObjectResponse, ), logoutAuth: requestSpec< @@ -1612,16 +1882,25 @@ const agentRequestSpecs = { >( schema.AGENT_METHODS.auth_logout, validate.zLogoutAuthRequest, + validate.zLogoutAuthResponse, emptyObjectResponse, ), unstable_startNes: requestSpec< schema.StartNesRequest, schema.StartNesResponse - >(schema.AGENT_METHODS.nes_start, validate.zStartNesRequest), + >( + schema.AGENT_METHODS.nes_start, + validate.zStartNesRequest, + validate.zStartNesResponse, + ), unstable_suggestNes: requestSpec< schema.SuggestNesRequest, schema.SuggestNesResponse - >(schema.AGENT_METHODS.nes_suggest, validate.zSuggestNesRequest), + >( + schema.AGENT_METHODS.nes_suggest, + validate.zSuggestNesRequest, + validate.zSuggestNesResponse, + ), unstable_closeNes: requestSpec< schema.CloseNesRequest, schema.CloseNesResponse | void, @@ -1629,6 +1908,7 @@ const agentRequestSpecs = { >( schema.AGENT_METHODS.nes_close, validate.zCloseNesRequest, + validate.zCloseNesResponse, emptyObjectResponse, ), }; @@ -1684,15 +1964,24 @@ const clientRequestSpecs = { >( schema.CLIENT_METHODS.session_request_permission, validate.zRequestPermissionRequest, + validate.zRequestPermissionResponse, ), unstable_connectMcp: requestSpec< schema.ConnectMcpRequest, schema.ConnectMcpResponse - >(schema.CLIENT_METHODS.mcp_connect, validate.zConnectMcpRequest), + >( + schema.CLIENT_METHODS.mcp_connect, + validate.zConnectMcpRequest, + validate.zConnectMcpResponse, + ), unstable_messageMcp: requestSpec< schema.MessageMcpRequest, schema.MessageMcpResponse - >(schema.CLIENT_METHODS.mcp_message, validate.zMessageMcpRequest), + >( + schema.CLIENT_METHODS.mcp_message, + validate.zMessageMcpRequest, + validate.zMessageMcpResponse, + ), unstable_disconnectMcp: requestSpec< schema.DisconnectMcpRequest, schema.DisconnectMcpResponse | void, @@ -1700,6 +1989,7 @@ const clientRequestSpecs = { >( schema.CLIENT_METHODS.mcp_disconnect, validate.zDisconnectMcpRequest, + validate.zDisconnectMcpResponse, emptyObjectResponse, ), unstable_createElicitation: requestSpec< @@ -1708,6 +1998,7 @@ const clientRequestSpecs = { >( schema.CLIENT_METHODS.elicitation_create, validate.zCreateElicitationRequest, + validate.zCreateElicitationResponse, ), }; @@ -2258,7 +2549,7 @@ export class AgentApp { handler: AgentRequestHandlersByMethod[Method], ): this; onRequest( - method: string, + method: ExtensionMethod, params: ParamsParser, handler: AgentRequestHandler, ): this; @@ -2300,7 +2591,7 @@ export class AgentApp { handler: AgentNotificationHandlersByMethod[Method], ): this; onNotification( - method: string, + method: ExtensionMethod, params: ParamsParser, handler: AgentNotificationHandler, ): this; @@ -2518,7 +2809,7 @@ export class ClientApp { handler: ClientRequestHandlersByMethod[Method], ): this; onRequest( - method: string, + method: ExtensionMethod, params: ParamsParser, handler: ClientRequestHandler, ): this; @@ -2560,7 +2851,7 @@ export class ClientApp { handler: ClientNotificationHandlersByMethod[Method], ): this; onNotification( - method: string, + method: ExtensionMethod, params: ParamsParser, handler: ClientNotificationHandler, ): this;