Skip to content

Commit 85675cd

Browse files
feat: add experimental namespace for URL elicitation
Move URL elicitation APIs to server.experimental and client.experimental to mirror Python SDK's approach for experimental features. - Add ExperimentalServerFeatures with elicitUrl() and createElicitationCompleteNotifier() - Add ExperimentalClientFeatures with setElicitationCompleteHandler() - Add /experimental subpath export for types (ElicitRequestURLParams, etc.) - Update examples and tests to use new .experimental API
1 parent 3485a06 commit 85675cd

File tree

10 files changed

+175
-17
lines changed

10 files changed

+175
-17
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@
4343
"import": "./dist/esm/validation/cfworker-provider.js",
4444
"require": "./dist/cjs/validation/cfworker-provider.js"
4545
},
46+
"./experimental": {
47+
"import": "./dist/esm/experimental/index.js",
48+
"require": "./dist/cjs/experimental/index.js"
49+
},
4650
"./*": {
4751
"import": "./dist/esm/*",
4852
"require": "./dist/cjs/*"

src/client/experimental.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Experimental client features.
3+
*
4+
* WARNING: These APIs are experimental and may change without notice.
5+
*
6+
* @module experimental
7+
*/
8+
9+
import { ElicitationCompleteNotificationSchema, type ElicitationCompleteNotification } from '../types.js';
10+
import type { AnyObjectSchema } from '../server/zod-compat.js';
11+
12+
/**
13+
* Handler for URL elicitation completion notifications.
14+
*/
15+
export type ElicitationCompleteHandler = (notification: ElicitationCompleteNotification) => void;
16+
17+
/**
18+
* Interface for the client methods used by experimental features.
19+
* @internal
20+
*/
21+
interface ClientLike {
22+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
23+
setNotificationHandler<T extends AnyObjectSchema>(schema: T, handler: (notification: any) => void): void;
24+
}
25+
26+
/**
27+
* Experimental client features for MCP.
28+
*
29+
* Access via `client.experimental`.
30+
*
31+
* WARNING: These APIs are experimental and may change without notice.
32+
*/
33+
export class ExperimentalClientFeatures {
34+
constructor(private client: ClientLike) {}
35+
36+
/**
37+
* Sets a handler for URL elicitation completion notifications.
38+
*
39+
* When a server completes an out-of-band URL elicitation interaction,
40+
* it sends a `notifications/elicitation/complete` notification.
41+
* This handler allows the client to react programmatically.
42+
*
43+
* @experimental This API may change without notice.
44+
* @param handler The handler function to call when a completion notification is received.
45+
*/
46+
setElicitationCompleteHandler(handler: ElicitationCompleteHandler): void {
47+
this.client.setNotificationHandler(ElicitationCompleteNotificationSchema, handler);
48+
}
49+
}

src/client/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { mergeCapabilities, Protocol, type ProtocolOptions, type RequestOptions } from '../shared/protocol.js';
2+
import { ExperimentalClientFeatures } from './experimental.js';
23
import type { Transport } from '../shared/transport.js';
34
import {
45
type CallToolRequest,
@@ -196,6 +197,13 @@ export class Client<
196197
private _jsonSchemaValidator: jsonSchemaValidator;
197198
private _cachedToolOutputValidators: Map<string, JsonSchemaValidator<unknown>> = new Map();
198199

200+
/**
201+
* Experimental client features.
202+
*
203+
* WARNING: These APIs are experimental and may change without notice.
204+
*/
205+
public readonly experimental: ExperimentalClientFeatures;
206+
199207
/**
200208
* Initializes this client with the given name and version information.
201209
*/
@@ -206,6 +214,7 @@ export class Client<
206214
super(options);
207215
this._capabilities = options?.capabilities ?? {};
208216
this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator();
217+
this.experimental = new ExperimentalClientFeatures(this);
209218
}
210219

211220
/**

src/examples/client/elicitationUrlExample.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,10 @@ import {
1717
ElicitRequest,
1818
ElicitResult,
1919
ResourceLink,
20-
ElicitRequestURLParams,
2120
McpError,
22-
ErrorCode,
23-
UrlElicitationRequiredError,
24-
ElicitationCompleteNotificationSchema
21+
ErrorCode
2522
} from '../../types.js';
23+
import { ElicitRequestURLParams, UrlElicitationRequiredError } from '../../experimental/index.js';
2624
import { getDisplayName } from '../../shared/metadataUtils.js';
2725
import { OAuthClientMetadata } from '../../shared/auth.js';
2826
import { exec } from 'node:child_process';
@@ -548,7 +546,7 @@ async function connect(url?: string): Promise<void> {
548546
client.setRequestHandler(ElicitRequestSchema, elicitationRequestHandler);
549547

550548
// Set up notification handler for elicitation completion
551-
client.setNotificationHandler(ElicitationCompleteNotificationSchema, notification => {
549+
client.experimental.setElicitationCompleteHandler(notification => {
552550
const { elicitationId } = notification.params;
553551
const pending = pendingURLElicitations.get(elicitationId);
554552
if (pending) {

src/examples/server/elicitationUrlExample.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import { McpServer } from '../../server/mcp.js';
1414
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
1515
import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter } from '../../server/auth/router.js';
1616
import { requireBearerAuth } from '../../server/auth/middleware/bearerAuth.js';
17-
import { CallToolResult, UrlElicitationRequiredError, ElicitRequestURLParams, ElicitResult, isInitializeRequest } from '../../types.js';
17+
import { CallToolResult, ElicitResult, isInitializeRequest } from '../../types.js';
18+
import { UrlElicitationRequiredError, ElicitRequestURLParams } from '../../experimental/index.js';
1819
import { InMemoryEventStore } from '../shared/inMemoryEventStore.js';
1920
import { setupAuthServer } from './demoInMemoryOAuthProvider.js';
2021
import { OAuthMetadata } from '../../shared/auth.js';
@@ -54,7 +55,7 @@ const getServer = () => {
5455

5556
// Create and track the elicitation
5657
const elicitationId = generateTrackedElicitation(sessionId, elicitationId =>
57-
mcpServer.server.createElicitationCompletionNotifier(elicitationId)
58+
mcpServer.server.experimental.createElicitationCompleteNotifier(elicitationId)
5859
);
5960
throw new UrlElicitationRequiredError([
6061
{
@@ -90,7 +91,7 @@ const getServer = () => {
9091

9192
// Create and track the elicitation
9293
const elicitationId = generateTrackedElicitation(sessionId, elicitationId =>
93-
mcpServer.server.createElicitationCompletionNotifier(elicitationId)
94+
mcpServer.server.experimental.createElicitationCompleteNotifier(elicitationId)
9495
);
9596

9697
// Simulate OAuth callback and token exchange after 5 seconds
@@ -623,8 +624,9 @@ const mcpPostHandler = async (req: Request, res: Response) => {
623624
console.log(`Session initialized with ID: ${sessionId}`);
624625
transports[sessionId] = transport;
625626
sessionsNeedingElicitation[sessionId] = {
626-
elicitationSender: params => server.server.elicitInput(params),
627-
createCompletionNotifier: elicitationId => server.server.createElicitationCompletionNotifier(elicitationId)
627+
elicitationSender: params => server.server.experimental.elicitUrl(params),
628+
createCompletionNotifier: elicitationId =>
629+
server.server.experimental.createElicitationCompleteNotifier(elicitationId)
628630
};
629631
}
630632
});

src/experimental/index.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/**
2+
* Experimental MCP features.
3+
*
4+
* APIs in this module may change without notice in future versions.
5+
* Use at your own risk.
6+
*
7+
* @module experimental
8+
*/
9+
10+
// URL Elicitation - experimental feature introduced in MCP 2025-11-25
11+
export {
12+
// Schemas
13+
ElicitRequestURLParamsSchema,
14+
ElicitationCompleteNotificationParamsSchema,
15+
ElicitationCompleteNotificationSchema,
16+
17+
// Types
18+
type ElicitRequestURLParams,
19+
type ElicitationCompleteNotificationParams,
20+
type ElicitationCompleteNotification,
21+
22+
// Error class
23+
UrlElicitationRequiredError
24+
} from '../types.js';
25+
26+
// Re-export experimental feature classes
27+
export { ExperimentalServerFeatures } from '../server/experimental.js';
28+
export { ExperimentalClientFeatures, type ElicitationCompleteHandler } from '../client/experimental.js';

src/server/experimental.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Experimental server features.
3+
*
4+
* WARNING: These APIs are experimental and may change without notice.
5+
*
6+
* @module experimental
7+
*/
8+
9+
import type { NotificationOptions, RequestOptions } from '../shared/protocol.js';
10+
import type { ElicitRequestURLParams, ElicitResult } from '../types.js';
11+
12+
/**
13+
* Interface for the server methods used by experimental features.
14+
* @internal
15+
*/
16+
interface ServerLike {
17+
elicitInput(params: ElicitRequestURLParams, options?: RequestOptions): Promise<ElicitResult>;
18+
createElicitationCompletionNotifier(elicitationId: string, options?: NotificationOptions): () => Promise<void>;
19+
}
20+
21+
/**
22+
* Experimental server features for MCP.
23+
*
24+
* Access via `server.experimental`.
25+
*
26+
* WARNING: These APIs are experimental and may change without notice.
27+
*/
28+
export class ExperimentalServerFeatures {
29+
constructor(private server: ServerLike) {}
30+
31+
/**
32+
* Creates a URL elicitation request.
33+
*
34+
* URL mode elicitation enables servers to direct users to external URLs
35+
* for out-of-band interactions that must not pass through the MCP client.
36+
* This is essential for auth flows, payment processing, and other sensitive operations.
37+
*
38+
* @experimental This API may change without notice.
39+
* @param params The URL elicitation parameters including url, message, and elicitationId.
40+
* @param options Optional request options.
41+
* @returns The result of the elicitation request.
42+
*/
43+
async elicitUrl(params: ElicitRequestURLParams, options?: RequestOptions): Promise<ElicitResult> {
44+
return this.server.elicitInput(params, options);
45+
}
46+
47+
/**
48+
* Creates a reusable callback that, when invoked, will send a `notifications/elicitation/complete`
49+
* notification for the specified elicitation ID.
50+
*
51+
* @experimental This API may change without notice.
52+
* @param elicitationId The ID of the elicitation to mark as complete.
53+
* @param options Optional notification options.
54+
* @returns A function that emits the completion notification when awaited.
55+
*/
56+
createElicitationCompleteNotifier(elicitationId: string, options?: NotificationOptions): () => Promise<void> {
57+
return this.server.createElicitationCompletionNotifier(elicitationId, options);
58+
}
59+
}

src/server/index.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import type { Transport } from '../shared/transport.js';
55
import {
66
CreateMessageRequestSchema,
77
ElicitRequestSchema,
8-
ElicitationCompleteNotificationSchema,
98
ErrorCode,
109
LATEST_PROTOCOL_VERSION,
1110
ListPromptsRequestSchema,
@@ -18,6 +17,7 @@ import {
1817
SetLevelRequestSchema,
1918
SUPPORTED_PROTOCOL_VERSIONS
2019
} from '../types.js';
20+
import { ElicitationCompleteNotificationSchema } from '../experimental/index.js';
2121
import { Server } from './index.js';
2222
import type { JsonSchemaType, JsonSchemaValidator, jsonSchemaValidator } from '../validation/types.js';
2323
import type { AnyObjectSchema } from './zod-compat.js';
@@ -925,15 +925,15 @@ test('should forward notification options when using elicitation completion noti
925925
}
926926
);
927927

928-
client.setNotificationHandler(ElicitationCompleteNotificationSchema, () => {});
928+
client.experimental.setElicitationCompleteHandler(() => {});
929929

930930
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
931931

932932
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
933933

934934
const notificationSpy = vi.spyOn(server, 'notification');
935935

936-
const notifier = server.createElicitationCompletionNotifier('elicitation-789', { relatedRequestId: 42 });
936+
const notifier = server.experimental.createElicitationCompleteNotifier('elicitation-789', { relatedRequestId: 42 });
937937
await notifier();
938938

939939
expect(notificationSpy).toHaveBeenCalledWith(
@@ -973,15 +973,15 @@ test('should create notifier that emits elicitation completion notification', as
973973
);
974974

975975
const receivedIds: string[] = [];
976-
client.setNotificationHandler(ElicitationCompleteNotificationSchema, notification => {
976+
client.experimental.setElicitationCompleteHandler(notification => {
977977
receivedIds.push(notification.params.elicitationId);
978978
});
979979

980980
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
981981

982982
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
983983

984-
const notifier = server.createElicitationCompletionNotifier('elicitation-123');
984+
const notifier = server.experimental.createElicitationCompleteNotifier('elicitation-123');
985985
await notifier();
986986

987987
await new Promise(resolve => setTimeout(resolve, 0));
@@ -1018,7 +1018,7 @@ test('should throw when creating notifier if client lacks URL elicitation suppor
10181018

10191019
await Promise.all([client.connect(clientTransport), server.connect(serverTransport)]);
10201020

1021-
expect(() => server.createElicitationCompletionNotifier('elicitation-123')).toThrow(
1021+
expect(() => server.experimental.createElicitationCompleteNotifier('elicitation-123')).toThrow(
10221022
'Client does not support URL elicitation (required for notifications/elicitation/complete)'
10231023
);
10241024
});

src/server/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { mergeCapabilities, Protocol, type NotificationOptions, type ProtocolOptions, type RequestOptions } from '../shared/protocol.js';
2+
import { ExperimentalServerFeatures } from './experimental.js';
23
import {
34
type ClientCapabilities,
45
type CreateMessageRequest,
@@ -117,6 +118,13 @@ export class Server<
117118
private _instructions?: string;
118119
private _jsonSchemaValidator: jsonSchemaValidator;
119120

121+
/**
122+
* Experimental server features.
123+
*
124+
* WARNING: These APIs are experimental and may change without notice.
125+
*/
126+
public readonly experimental: ExperimentalServerFeatures;
127+
120128
/**
121129
* Callback for when initialization has fully completed (i.e., the client has sent an `initialized` notification).
122130
*/
@@ -133,6 +141,7 @@ export class Server<
133141
this._capabilities = options?.capabilities ?? {};
134142
this._instructions = options?.instructions;
135143
this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator();
144+
this.experimental = new ExperimentalServerFeatures(this);
136145

137146
this.setRequestHandler(InitializeRequestSchema, request => this._oninitialize(request));
138147
this.setNotificationHandler(InitializedNotificationSchema, () => this.oninitialized?.());

src/server/mcp.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ import {
1515
type Notification,
1616
ReadResourceResultSchema,
1717
type TextContent,
18-
UrlElicitationRequiredError,
1918
ErrorCode
2019
} from '../types.js';
20+
import { UrlElicitationRequiredError } from '../experimental/index.js';
2121
import { completable } from './completable.js';
2222
import { McpServer, ResourceTemplate } from './mcp.js';
2323
import { zodTestMatrix, type ZodMatrixEntry } from '../shared/zodTestMatrix.js';

0 commit comments

Comments
 (0)