Skip to content

Commit 71abccb

Browse files
Merge branch 'main' into request-propagation-in-tools
2 parents e714050 + 61ef3c9 commit 71abccb

File tree

10 files changed

+1837
-249
lines changed

10 files changed

+1837
-249
lines changed

README.md

Lines changed: 239 additions & 24 deletions
Large diffs are not rendered by default.

src/examples/client/simpleStreamableHttp.ts

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import {
1414
ListResourcesResultSchema,
1515
LoggingMessageNotificationSchema,
1616
ResourceListChangedNotificationSchema,
17+
ReadResourceRequest,
18+
ReadResourceResultSchema,
19+
ResourceLink,
1720
} from '../../types.js';
21+
import { getDisplayName } from '../../shared/metadataUtils.js';
1822

1923
// Create readline interface for user input
2024
const readline = createInterface({
@@ -59,6 +63,7 @@ function printHelp(): void {
5963
console.log(' list-prompts - List available prompts');
6064
console.log(' get-prompt [name] [args] - Get a prompt with optional JSON arguments');
6165
console.log(' list-resources - List available resources');
66+
console.log(' read-resource <uri> - Read a specific resource by URI');
6267
console.log(' help - Show this help');
6368
console.log(' quit - Exit the program');
6469
}
@@ -154,6 +159,14 @@ function commandLoop(): void {
154159
await listResources();
155160
break;
156161

162+
case 'read-resource':
163+
if (args.length < 2) {
164+
console.log('Usage: read-resource <uri>');
165+
} else {
166+
await readResource(args[1]);
167+
}
168+
break;
169+
157170
case 'help':
158171
printHelp();
159172
break;
@@ -317,7 +330,7 @@ async function listTools(): Promise<void> {
317330
console.log(' No tools available');
318331
} else {
319332
for (const tool of toolsResult.tools) {
320-
console.log(` - ${tool.name}: ${tool.description}`);
333+
console.log(` - id: ${tool.name}, name: ${getDisplayName(tool)}, description: ${tool.description}`);
321334
}
322335
}
323336
} catch (error) {
@@ -344,13 +357,37 @@ async function callTool(name: string, args: Record<string, unknown>): Promise<vo
344357
const result = await client.request(request, CallToolResultSchema);
345358

346359
console.log('Tool result:');
360+
const resourceLinks: ResourceLink[] = [];
361+
347362
result.content.forEach(item => {
348363
if (item.type === 'text') {
349364
console.log(` ${item.text}`);
365+
} else if (item.type === 'resource_link') {
366+
const resourceLink = item as ResourceLink;
367+
resourceLinks.push(resourceLink);
368+
console.log(` 📁 Resource Link: ${resourceLink.name}`);
369+
console.log(` URI: ${resourceLink.uri}`);
370+
if (resourceLink.mimeType) {
371+
console.log(` Type: ${resourceLink.mimeType}`);
372+
}
373+
if (resourceLink.description) {
374+
console.log(` Description: ${resourceLink.description}`);
375+
}
376+
} else if (item.type === 'resource') {
377+
console.log(` [Embedded Resource: ${item.resource.uri}]`);
378+
} else if (item.type === 'image') {
379+
console.log(` [Image: ${item.mimeType}]`);
380+
} else if (item.type === 'audio') {
381+
console.log(` [Audio: ${item.mimeType}]`);
350382
} else {
351-
console.log(` ${item.type} content:`, item);
383+
console.log(` [Unknown content type]:`, item);
352384
}
353385
});
386+
387+
// Offer to read resource links
388+
if (resourceLinks.length > 0) {
389+
console.log(`\nFound ${resourceLinks.length} resource link(s). Use 'read-resource <uri>' to read their content.`);
390+
}
354391
} catch (error) {
355392
console.log(`Error calling tool ${name}: ${error}`);
356393
}
@@ -380,7 +417,7 @@ async function runNotificationsToolWithResumability(interval: number, count: num
380417
try {
381418
console.log(`Starting notification stream with resumability: interval=${interval}ms, count=${count || 'unlimited'}`);
382419
console.log(`Using resumption token: ${notificationsToolLastEventId || 'none'}`);
383-
420+
384421
const request: CallToolRequest = {
385422
method: 'tools/call',
386423
params: {
@@ -393,7 +430,7 @@ async function runNotificationsToolWithResumability(interval: number, count: num
393430
notificationsToolLastEventId = event;
394431
console.log(`Updated resumption token: ${event}`);
395432
};
396-
433+
397434
const result = await client.request(request, CallToolResultSchema, {
398435
resumptionToken: notificationsToolLastEventId,
399436
onresumptiontoken: onLastEventIdUpdate
@@ -429,7 +466,7 @@ async function listPrompts(): Promise<void> {
429466
console.log(' No prompts available');
430467
} else {
431468
for (const prompt of promptsResult.prompts) {
432-
console.log(` - ${prompt.name}: ${prompt.description}`);
469+
console.log(` - id: ${prompt.name}, name: ${getDisplayName(prompt)}, description: ${prompt.description}`);
433470
}
434471
}
435472
} catch (error) {
@@ -480,14 +517,50 @@ async function listResources(): Promise<void> {
480517
console.log(' No resources available');
481518
} else {
482519
for (const resource of resourcesResult.resources) {
483-
console.log(` - ${resource.name}: ${resource.uri}`);
520+
console.log(` - id: ${resource.name}, name: ${getDisplayName(resource)}, description: ${resource.uri}`);
484521
}
485522
}
486523
} catch (error) {
487524
console.log(`Resources not supported by this server (${error})`);
488525
}
489526
}
490527

528+
async function readResource(uri: string): Promise<void> {
529+
if (!client) {
530+
console.log('Not connected to server.');
531+
return;
532+
}
533+
534+
try {
535+
const request: ReadResourceRequest = {
536+
method: 'resources/read',
537+
params: { uri }
538+
};
539+
540+
console.log(`Reading resource: ${uri}`);
541+
const result = await client.request(request, ReadResourceResultSchema);
542+
543+
console.log('Resource contents:');
544+
for (const content of result.contents) {
545+
console.log(` URI: ${content.uri}`);
546+
if (content.mimeType) {
547+
console.log(` Type: ${content.mimeType}`);
548+
}
549+
550+
if ('text' in content && typeof content.text === 'string') {
551+
console.log(' Content:');
552+
console.log(' ---');
553+
console.log(content.text.split('\n').map((line: string) => ' ' + line).join('\n'));
554+
console.log(' ---');
555+
} else if ('blob' in content && typeof content.blob === 'string') {
556+
console.log(` [Binary data: ${content.blob.length} bytes]`);
557+
}
558+
}
559+
} catch (error) {
560+
console.log(`Error reading resource ${uri}: ${error}`);
561+
}
562+
}
563+
491564
async function cleanup(): Promise<void> {
492565
if (client && transport) {
493566
try {

src/examples/server/simpleStreamableHttp.ts

Lines changed: 114 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { McpServer } from '../../server/mcp.js';
55
import { StreamableHTTPServerTransport } from '../../server/streamableHttp.js';
66
import { getOAuthProtectedResourceMetadataUrl, mcpAuthMetadataRouter } from '../../server/auth/router.js';
77
import { requireBearerAuth } from '../../server/auth/middleware/bearerAuth.js';
8-
import { CallToolResult, GetPromptResult, isInitializeRequest, ReadResourceResult } from '../../types.js';
8+
import { CallToolResult, GetPromptResult, isInitializeRequest, ReadResourceResult, ResourceLink } from '../../types.js';
99
import { InMemoryEventStore } from '../shared/inMemoryEventStore.js';
1010
import { setupAuthServer } from './demoInMemoryOAuthProvider.js';
1111
import { OAuthMetadata } from 'src/shared/auth.js';
@@ -17,15 +17,18 @@ const useOAuth = process.argv.includes('--oauth');
1717
const getServer = () => {
1818
const server = new McpServer({
1919
name: 'simple-streamable-http-server',
20-
version: '1.0.0',
20+
version: '1.0.0'
2121
}, { capabilities: { logging: {} } });
2222

2323
// Register a simple tool that returns a greeting
24-
server.tool(
24+
server.registerTool(
2525
'greet',
26-
'A simple greeting tool',
2726
{
28-
name: z.string().describe('Name to greet'),
27+
title: 'Greeting Tool', // Display name for UI
28+
description: 'A simple greeting tool',
29+
inputSchema: {
30+
name: z.string().describe('Name to greet'),
31+
},
2932
},
3033
async ({ name }): Promise<CallToolResult> => {
3134
return {
@@ -84,12 +87,15 @@ const getServer = () => {
8487
}
8588
);
8689

87-
// Register a simple prompt
88-
server.prompt(
90+
// Register a simple prompt with title
91+
server.registerPrompt(
8992
'greeting-template',
90-
'A simple greeting prompt template',
9193
{
92-
name: z.string().describe('Name to include in greeting'),
94+
title: 'Greeting Template', // Display name for UI
95+
description: 'A simple greeting prompt template',
96+
argsSchema: {
97+
name: z.string().describe('Name to include in greeting'),
98+
},
9399
},
94100
async ({ name }): Promise<GetPromptResult> => {
95101
return {
@@ -148,10 +154,14 @@ const getServer = () => {
148154
);
149155

150156
// Create a simple resource at a fixed URI
151-
server.resource(
157+
server.registerResource(
152158
'greeting-resource',
153159
'https://example.com/greetings/default',
154-
{ mimeType: 'text/plain' },
160+
{
161+
title: 'Default Greeting', // Display name for UI
162+
description: 'A simple greeting resource',
163+
mimeType: 'text/plain'
164+
},
155165
async (): Promise<ReadResourceResult> => {
156166
return {
157167
contents: [
@@ -163,6 +173,99 @@ const getServer = () => {
163173
};
164174
}
165175
);
176+
177+
// Create additional resources for ResourceLink demonstration
178+
server.registerResource(
179+
'example-file-1',
180+
'file:///example/file1.txt',
181+
{
182+
title: 'Example File 1',
183+
description: 'First example file for ResourceLink demonstration',
184+
mimeType: 'text/plain'
185+
},
186+
async (): Promise<ReadResourceResult> => {
187+
return {
188+
contents: [
189+
{
190+
uri: 'file:///example/file1.txt',
191+
text: 'This is the content of file 1',
192+
},
193+
],
194+
};
195+
}
196+
);
197+
198+
server.registerResource(
199+
'example-file-2',
200+
'file:///example/file2.txt',
201+
{
202+
title: 'Example File 2',
203+
description: 'Second example file for ResourceLink demonstration',
204+
mimeType: 'text/plain'
205+
},
206+
async (): Promise<ReadResourceResult> => {
207+
return {
208+
contents: [
209+
{
210+
uri: 'file:///example/file2.txt',
211+
text: 'This is the content of file 2',
212+
},
213+
],
214+
};
215+
}
216+
);
217+
218+
// Register a tool that returns ResourceLinks
219+
server.registerTool(
220+
'list-files',
221+
{
222+
title: 'List Files with ResourceLinks',
223+
description: 'Returns a list of files as ResourceLinks without embedding their content',
224+
inputSchema: {
225+
includeDescriptions: z.boolean().optional().describe('Whether to include descriptions in the resource links'),
226+
},
227+
},
228+
async ({ includeDescriptions = true }): Promise<CallToolResult> => {
229+
const resourceLinks: ResourceLink[] = [
230+
{
231+
type: 'resource_link',
232+
uri: 'https://example.com/greetings/default',
233+
name: 'Default Greeting',
234+
mimeType: 'text/plain',
235+
...(includeDescriptions && { description: 'A simple greeting resource' })
236+
},
237+
{
238+
type: 'resource_link',
239+
uri: 'file:///example/file1.txt',
240+
name: 'Example File 1',
241+
mimeType: 'text/plain',
242+
...(includeDescriptions && { description: 'First example file for ResourceLink demonstration' })
243+
},
244+
{
245+
type: 'resource_link',
246+
uri: 'file:///example/file2.txt',
247+
name: 'Example File 2',
248+
mimeType: 'text/plain',
249+
...(includeDescriptions && { description: 'Second example file for ResourceLink demonstration' })
250+
}
251+
];
252+
253+
return {
254+
content: [
255+
{
256+
type: 'text',
257+
text: 'Here are the available files as resource links:',
258+
},
259+
...resourceLinks,
260+
{
261+
type: 'text',
262+
text: '\nYou can read any of these resources using their URI.',
263+
}
264+
],
265+
};
266+
}
267+
);
268+
166269
return server;
167270
};
168271

src/server/completable.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ export enum McpZodTypeKind {
1515

1616
export type CompleteCallback<T extends ZodTypeAny = ZodTypeAny> = (
1717
value: T["_input"],
18+
context?: {
19+
arguments?: Record<string, string>;
20+
},
1821
) => T["_input"][] | Promise<T["_input"][]>;
1922

2023
export interface CompletableDef<T extends ZodTypeAny = ZodTypeAny>

0 commit comments

Comments
 (0)