Skip to content

Commit 1f5fa16

Browse files
docs(core): clarify schema-bundle overload validates only (no outbound transforms)
Reverts 0e053fe. The params schema in the {params, result} bundle is a type guard, not a transformer: the value the caller passes is sent as-is, matching v1 and request() behavior. parsed.data is intentionally unused on the outbound path — validation catches shape errors before the round-trip, but transforms/defaults are an inbound concern (parsing untrusted wire data), not an outbound one (sending trusted local data). Adds JSDoc note and a test asserting params are sent verbatim.
1 parent 0e053fe commit 1f5fa16

File tree

3 files changed

+13
-15
lines changed

3 files changed

+13
-15
lines changed

packages/core/src/shared/protocol.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import {
4646
ProtocolErrorCode,
4747
SUPPORTED_PROTOCOL_VERSIONS
4848
} from '../types/index.js';
49-
import type { AnySchema, SchemaInput, SchemaOutput } from '../util/schema.js';
49+
import type { AnySchema, SchemaOutput } from '../util/schema.js';
5050
import { parseSchema } from '../util/schema.js';
5151
import type { TaskContext, TaskManagerHost, TaskManagerOptions, TaskRequestOptions } from './taskManager.js';
5252
import { NullTaskManager, TaskManager } from './taskManager.js';
@@ -1143,10 +1143,14 @@ export abstract class Protocol<ContextT extends BaseContext> {
11431143
*
11441144
* Pass a `{ params, result }` schema bundle as the third argument to get typed `params` and
11451145
* pre-send validation; pass a bare result schema for loose, unvalidated params.
1146+
*
1147+
* The `params` schema is used only for validation — the value you pass is sent as-is.
1148+
* Transforms (e.g. `.trim()`) and defaults (e.g. `.default(n)`) on the schema are not
1149+
* applied to outbound data, matching the behavior of {@linkcode Protocol.request | request}.
11461150
*/
11471151
sendCustomRequest<P extends AnySchema, R extends AnySchema>(
11481152
method: string,
1149-
params: SchemaInput<P>,
1153+
params: SchemaOutput<P>,
11501154
schemas: { params: P; result: R },
11511155
options?: RequestOptions
11521156
): Promise<SchemaOutput<R>>;
@@ -1168,7 +1172,6 @@ export abstract class Protocol<ContextT extends BaseContext> {
11681172
if (!parsed.success) {
11691173
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid params for ${method}: ${parsed.error.message}`);
11701174
}
1171-
params = parsed.data as Record<string, unknown> | undefined;
11721175
resultSchema = schemaOrBundle.result;
11731176
} else {
11741177
resultSchema = schemaOrBundle;
@@ -1186,11 +1189,12 @@ export abstract class Protocol<ContextT extends BaseContext> {
11861189
* standard MCP notifications.
11871190
*
11881191
* Pass a `{ params }` schema bundle as the third argument to get typed `params` and pre-send
1189-
* validation.
1192+
* validation. The schema validates only — transforms and defaults are not applied to
1193+
* outbound data; the value you pass is sent as-is.
11901194
*/
11911195
sendCustomNotification<P extends AnySchema>(
11921196
method: string,
1193-
params: SchemaInput<P>,
1197+
params: SchemaOutput<P>,
11941198
schemas: { params: P },
11951199
options?: NotificationOptions
11961200
): Promise<void>;
@@ -1207,7 +1211,6 @@ export abstract class Protocol<ContextT extends BaseContext> {
12071211
if (!parsed.success) {
12081212
throw new ProtocolError(ProtocolErrorCode.InvalidParams, `Invalid params for ${method}: ${parsed.error.message}`);
12091213
}
1210-
params = parsed.data as Record<string, unknown> | undefined;
12111214
options = maybeOptions;
12121215
} else {
12131216
options = schemasOrOptions;

packages/core/src/util/schema.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@ export type AnyObjectSchema = z.core.$ZodObject;
2020
*/
2121
export type SchemaOutput<T extends AnySchema> = z.output<T>;
2222

23-
/**
24-
* Extracts the input type from a Zod schema (pre-transform / pre-default).
25-
*/
26-
export type SchemaInput<T extends AnySchema> = z.input<T>;
27-
2823
/**
2924
* Parses data against a Zod schema (synchronous).
3025
* Returns a discriminated union with success/error.

packages/core/test/shared/customMethods.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -192,16 +192,16 @@ describe('sendCustomRequest', () => {
192192
).rejects.toSatisfy((e: unknown) => e instanceof ProtocolError && e.code === ProtocolErrorCode.InvalidParams);
193193
});
194194

195-
test('schema bundle overload: transforms and defaults applied to outbound params', async () => {
195+
test('schema bundle overload: params sent as-is (validate-only, no outbound transforms)', async () => {
196196
const [client, server] = await linkedPair();
197-
const ParamsWithTransforms = z.object({ query: z.string().trim(), page: z.number().default(1) });
197+
const P = z.object({ query: z.string().transform(s => s.trim()), page: z.number() });
198198
let received: unknown;
199199
server.setCustomRequestHandler('acme/q', z.unknown(), p => {
200200
received = p;
201201
return {};
202202
});
203-
await client.sendCustomRequest('acme/q', { query: ' hi ' }, { params: ParamsWithTransforms, result: z.object({}) });
204-
expect(received).toEqual({ query: 'hi', page: 1 });
203+
await client.sendCustomRequest('acme/q', { query: ' hi ', page: 1 }, { params: P, result: z.object({}) });
204+
expect(received).toEqual({ query: ' hi ', page: 1 });
205205
});
206206
});
207207

0 commit comments

Comments
 (0)