Skip to content

Commit 97b7646

Browse files
committed
Finish updating examples, rename
1 parent d9dff46 commit 97b7646

File tree

6 files changed

+94
-110
lines changed

6 files changed

+94
-110
lines changed

src/examples/README.md

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@ This directory contains example implementations of MCP clients and servers using
77
- [Client Implementations](#client-implementations)
88
- [Streamable HTTP Client](#streamable-http-client)
99
- [Backwards Compatible Client](#backwards-compatible-client)
10+
- [URL Elicitation Example Client](#url-elicitation-example-client)
1011
- [Server Implementations](#server-implementations)
1112
- [Single Node Deployment](#single-node-deployment)
1213
- [Streamable HTTP Transport](#streamable-http-transport)
1314
- [Deprecated SSE Transport](#deprecated-sse-transport)
1415
- [Backwards Compatible Server](#streamable-http-backwards-compatible-server-with-sse)
16+
- [Form Elicitation Example](#form-elicitation-example)
17+
- [URL Elicitation Example](#url-elicitation-example)
1518
- [Multi-Node Deployment](#multi-node-deployment)
1619
- [Backwards Compatibility](#testing-streamable-http-backwards-compatibility-with-sse)
1720

@@ -51,6 +54,17 @@ A client that implements backwards compatibility according to the [MCP specifica
5154
npx tsx src/examples/client/streamableHttpWithSseFallbackClient.ts
5255
```
5356

57+
### URL Elicitation Example Client
58+
59+
A client that demonstrates how to use URL elicitation to securely collect _sensitive_ user input or perform secure third-party flows.
60+
61+
```bash
62+
npx tsx src/examples/client/elicitationUrlExample.ts
63+
64+
# Separately, run the server as well:
65+
npx tsx src/examples/server/elicitationUrlExample.ts
66+
```
67+
5468
## Server Implementations
5569

5670
### Single Node Deployment
@@ -105,7 +119,15 @@ A server that demonstrates server notifications using Streamable HTTP.
105119
npx tsx src/examples/server/standaloneSseWithGetStreamableHttp.ts
106120
```
107121

108-
##### Elicitation Example
122+
##### Form Elicitation Example
123+
124+
A server that demonstrates using form elicitation to collect _non-sensitive_ user input.
125+
126+
```bash
127+
npx tsx src/examples/server/elicitationFormExample.ts
128+
```
129+
130+
##### URL Elicitation Example
109131

110132
A comprehensive example demonstrating URL mode elicitation with a server protected by MCP authorization. This example shows:
111133

@@ -116,11 +138,11 @@ A comprehensive example demonstrating URL mode elicitation with a server protect
116138
To run this example:
117139

118140
```bash
119-
# Start the server with OAuth enabled
120-
npx tsx src/examples/server/elicitationStreamableHttp.ts
141+
# Start the server
142+
npx tsx src/examples/server/elicitationUrlExample.ts
121143

122-
# In a separate terminal, start the client with OAuth
123-
npx tsx src/examples/client/elicitationStreamableHttp.ts
144+
# In a separate terminal, start the client
145+
npx tsx src/examples/client/elicitationUrlExample.ts
124146
```
125147

126148
#### Deprecated SSE Transport

src/examples/client/elicitationStreamableHttp.ts renamed to src/examples/client/elicitationUrlExample.ts

Lines changed: 40 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
// Run with: npx tsx src/examples/client/elicitationUrlExample.ts
2+
//
3+
// This example demonstrates how to use URL elicitation to securely
4+
// collect user input in a remote (HTTP) server.
5+
// URL elicitation allows servers to prompt the end-user to open a URL in their browser
6+
// to collect sensitive information.
7+
18
import { Client } from '../../client/index.js';
29
import { StreamableHTTPClientTransport } from '../../client/streamableHttp.js';
310
import { createInterface } from 'node:readline';
@@ -489,7 +496,8 @@ async function connect(url?: string): Promise<void> {
489496
{
490497
capabilities: {
491498
elicitation: {
492-
form: {},
499+
// Only URL elicitation is supported in this demo
500+
// (see server/elicitationExample.ts for a demo of form mode elicitation)
493501
url: {}
494502
}
495503
}
@@ -499,13 +507,34 @@ async function connect(url?: string): Promise<void> {
499507
// Only create a new transport if one doesn't exist
500508
transport = new StreamableHTTPClientTransport(new URL(serverUrl), {
501509
sessionId: sessionId,
502-
authProvider: oauthProvider
510+
authProvider: oauthProvider,
511+
requestInit: {
512+
headers: {
513+
'Content-Type': 'application/json',
514+
Accept: 'application/json, text/event-stream'
515+
}
516+
}
503517
});
504518
}
505519

506520
// Set up elicitation request handler with proper validation
507521
client.setRequestHandler(ElicitRequestSchema, elicitationRequestHandler);
508522

523+
// Set up notification handler for elicitation completion
524+
client.setNotificationHandler(ElicitationCompleteNotificationSchema, notification => {
525+
const { elicitationId } = notification.params;
526+
const pending = pendingURLElicitations.get(elicitationId);
527+
if (pending) {
528+
clearTimeout(pending.timeout);
529+
pendingURLElicitations.delete(elicitationId);
530+
console.log(`\x1b[32m✅ Elicitation ${elicitationId} completed!\x1b[0m`);
531+
pending.resolve();
532+
} else {
533+
// Shouldn't happen - discard it!
534+
console.warn(`Received completion notification for unknown elicitation: ${elicitationId}`);
535+
}
536+
});
537+
509538
try {
510539
console.log(`Connecting to ${serverUrl}...`);
511540
// Connect the client
@@ -521,40 +550,6 @@ async function connect(url?: string): Promise<void> {
521550
await transport.finishAuth(authCode);
522551
console.log('🔐 Authorization code received:', authCode);
523552
console.log('🔌 Reconnecting with authenticated transport...');
524-
transport = new StreamableHTTPClientTransport(new URL(serverUrl), {
525-
sessionId: sessionId,
526-
authProvider: oauthProvider
527-
});
528-
await client.connect(transport);
529-
} else {
530-
console.error('Failed to connect:', error);
531-
client = null;
532-
transport = null;
533-
return;
534-
}
535-
536-
if (url) {
537-
serverUrl = url;
538-
}
539-
540-
// Create a new client with elicitation capability
541-
client = new Client(
542-
{
543-
name: 'example-client',
544-
version: '1.0.0'
545-
},
546-
{
547-
capabilities: {
548-
elicitation: {
549-
// Only URL elicitation is supported in this demo
550-
// (see server/elicitationExample.ts for a demo of form mode elicitation)
551-
url: {}
552-
}
553-
}
554-
}
555-
);
556-
if (!transport) {
557-
// Only create a new transport if one doesn't exist
558553
transport = new StreamableHTTPClientTransport(new URL(serverUrl), {
559554
sessionId: sessionId,
560555
authProvider: oauthProvider,
@@ -565,64 +560,18 @@ async function connect(url?: string): Promise<void> {
565560
}
566561
}
567562
});
568-
}
569-
570-
// Set up elicitation request handler with proper validation
571-
client.setRequestHandler(ElicitRequestSchema, elicitationRequestHandler);
572-
573-
// Set up notification handler for elicitation completion
574-
client.setNotificationHandler(ElicitationCompleteNotificationSchema, notification => {
575-
const { elicitationId } = notification.params;
576-
const pending = pendingURLElicitations.get(elicitationId);
577-
if (pending) {
578-
clearTimeout(pending.timeout);
579-
pendingURLElicitations.delete(elicitationId);
580-
console.log(`\x1b[32m✅ Elicitation ${elicitationId} completed!\x1b[0m`);
581-
pending.resolve();
582-
} else {
583-
// Shouldn't happen - discard it!
584-
console.warn(`Received completion notification for unknown elicitation: ${elicitationId}`);
585-
}
586-
});
587-
588-
try {
589-
console.log(`Connecting to ${serverUrl}...`);
590-
// Connect the client
591563
await client.connect(transport);
592-
sessionId = transport.sessionId;
593-
console.log('Transport created with session ID:', sessionId);
594-
console.log('Connected to MCP server');
595-
} catch (error) {
596-
if (error instanceof UnauthorizedError) {
597-
console.log('OAuth required - waiting for authorization...');
598-
const callbackPromise = waitForOAuthCallback();
599-
const authCode = await callbackPromise;
600-
await transport.finishAuth(authCode);
601-
console.log('🔐 Authorization code received:', authCode);
602-
console.log('🔌 Reconnecting with authenticated transport...');
603-
transport = new StreamableHTTPClientTransport(new URL(serverUrl), {
604-
sessionId: sessionId,
605-
authProvider: oauthProvider,
606-
requestInit: {
607-
headers: {
608-
'Content-Type': 'application/json',
609-
Accept: 'application/json, text/event-stream'
610-
}
611-
}
612-
});
613-
await client.connect(transport);
614-
} else {
615-
console.error('Failed to connect:', error);
616-
client = null;
617-
transport = null;
618-
return;
619-
}
564+
} else {
565+
console.error('Failed to connect:', error);
566+
client = null;
567+
transport = null;
568+
return;
620569
}
621-
// Set up error handler after connection is established so we don't double log errors
622-
client.onerror = error => {
623-
console.error('\x1b[31mClient error:', error, '\x1b[0m');
624-
};
625570
}
571+
// Set up error handler after connection is established so we don't double log errors
572+
client.onerror = error => {
573+
console.error('\x1b[31mClient error:', error, '\x1b[0m');
574+
};
626575
}
627576

628577
async function disconnect(): Promise<void> {

src/examples/client/simpleStreamableHttp.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function printHelp(): void {
6262
console.log(' call-tool <name> [args] - Call a tool with optional JSON arguments');
6363
console.log(' greet [name] - Call the greet tool');
6464
console.log(' multi-greet [name] - Call the multi-greet tool with notifications');
65-
console.log(' collect-info [type] - Test elicitation with collect-user-info tool (contact/preferences/feedback)');
65+
console.log(' collect-info [type] - Test form elicitation with collect-user-info tool (contact/preferences/feedback)');
6666
console.log(' start-notifications [interval] [count] - Start periodic notifications');
6767
console.log(' run-notifications-tool-with-resumability [interval] [count] - Run notification tool with resumability');
6868
console.log(' list-prompts - List available prompts');
@@ -213,15 +213,17 @@ async function connect(url?: string): Promise<void> {
213213
console.log(`Connecting to ${serverUrl}...`);
214214

215215
try {
216-
// Create a new client with elicitation capability
216+
// Create a new client with form elicitation capability
217217
client = new Client(
218218
{
219219
name: 'example-client',
220220
version: '1.0.0'
221221
},
222222
{
223223
capabilities: {
224-
elicitation: {}
224+
elicitation: {
225+
form: {}
226+
}
225227
}
226228
}
227229
);
@@ -234,7 +236,7 @@ async function connect(url?: string): Promise<void> {
234236
if (request.params.mode !== 'form') {
235237
throw new McpError(ErrorCode.InvalidParams, `Unsupported elicitation mode: ${request.params.mode}`);
236238
}
237-
console.log('\n🔔 Elicitation Request Received:');
239+
console.log('\n🔔 Elicitation (form) Request Received:');
238240
console.log(`Message: ${request.params.message}`);
239241
console.log('Requested Schema:');
240242
console.log(JSON.stringify(request.params.requestedSchema, null, 2));
@@ -615,7 +617,7 @@ async function callMultiGreetTool(name: string): Promise<void> {
615617
}
616618

617619
async function callCollectInfoTool(infoType: string): Promise<void> {
618-
console.log(`Testing elicitation with collect-user-info tool (${infoType})...`);
620+
console.log(`Testing form elicitation with collect-user-info tool (${infoType})...`);
619621
await callTool('collect-user-info', { infoType });
620622
}
621623

src/examples/server/elicitationExample.ts renamed to src/examples/server/elicitationFormExample.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
// Run with: npx tsx src/examples/server/elicitationExample.ts
1+
// Run with: npx tsx src/examples/server/elicitationFormExample.ts
22
//
3-
// This example demonstrates how to use elicitation to collect structured user input
3+
// This example demonstrates how to use form elicitation to collect structured user input
44
// with JSON Schema validation via a local HTTP server with SSE streaming.
5-
// Elicitation allows servers to request user input through the client interface
5+
// Form elicitation allows servers to request *non-sensitive* user input through the client
66
// with schema-based validation.
7+
// Note: See also elicitationUrlExample.ts for an example of using URL elicitation
8+
// to collect *sensitive* user input via a browser.
79

810
import { randomUUID } from 'node:crypto';
911
import cors from 'cors';
@@ -16,7 +18,7 @@ import { isInitializeRequest } from '../../types.js';
1618
// The validator supports format validation (email, date, etc.) if ajv-formats is installed
1719
const mcpServer = new McpServer(
1820
{
19-
name: 'elicitation-example-server',
21+
name: 'form-elicitation-example-server',
2022
version: '1.0.0'
2123
},
2224
{
@@ -36,7 +38,7 @@ mcpServer.registerTool(
3638
},
3739
async () => {
3840
try {
39-
// Request user information through elicitation
41+
// Request user information through form elicitation
4042
const result = await mcpServer.server.elicitFormInput({
4143
message: 'Please provide your registration information:',
4244
requestedSchema: {
@@ -123,7 +125,7 @@ mcpServer.registerTool(
123125
);
124126

125127
/**
126-
* Example 2: Multi-step workflow with multiple elicitation requests
128+
* Example 2: Multi-step workflow with multiple form elicitation requests
127129
* Demonstrates how to collect information in multiple steps
128130
*/
129131
mcpServer.registerTool(
@@ -441,7 +443,7 @@ async function main() {
441443
console.error('Failed to start server:', error);
442444
process.exit(1);
443445
}
444-
console.log(`Elicitation example server is running on http://localhost:${PORT}/mcp`);
446+
console.log(`Form elicitation example server is running on http://localhost:${PORT}/mcp`);
445447
console.log('Available tools:');
446448
console.log(' - register_user: Collect user registration information');
447449
console.log(' - create_event: Multi-step event creation');

src/examples/server/elicitationStreamableHttp.ts renamed to src/examples/server/elicitationUrlExample.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
// Run with: npx tsx src/examples/server/elicitationUrlExample.ts
2+
//
3+
// This example demonstrates how to use URL elicitation to securely collect
4+
// *sensitive* user input in a remote (HTTP) server.
5+
// URL elicitation allows servers to prompt the end-user to open a URL in their browser
6+
// to collect sensitive information.
7+
// Note: See also elicitationFormExample.ts for an example of using form (not URL) elicitation
8+
// to collect *non-sensitive* user input with a structured schema.
9+
110
import express, { Request, Response } from 'express';
211
import { randomUUID } from 'node:crypto';
312
import { z } from 'zod';

src/examples/server/simpleStreamableHttp.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ const getServer = () => {
111111
};
112112
}
113113
);
114-
// Register a tool that demonstrates elicitation (user input collection)
114+
// Register a tool that demonstrates form elicitation (user input collection with a schema)
115115
// This creates a closure that captures the server instance
116116
server.tool(
117117
'collect-user-info',
118-
'A tool that collects user information through elicitation',
118+
'A tool that collects user information through form elicitation',
119119
{
120120
infoType: z.enum(['contact', 'preferences', 'feedback']).describe('Type of information to collect')
121121
},

0 commit comments

Comments
 (0)