Skip to content

Commit 7241f1f

Browse files
feat(mcp-adapters): Add resource management methods and structured content support (#9515)
Co-authored-by: Christian Bromann <git@bromann.dev>
1 parent 2eba22e commit 7241f1f

File tree

7 files changed

+675
-17
lines changed

7 files changed

+675
-17
lines changed

.changeset/fifty-plants-drive.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@langchain/mcp-adapters": minor
3+
---
4+
5+
feat(mcp-adapters): Add resource management methods and structured content support

libs/langchain-mcp-adapters/src/client.ts

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ import {
2323
type ResolvedStdioConnection,
2424
type ResolvedStreamableHTTPConnection,
2525
type CustomHTTPTransportOptions,
26+
type MCPResource,
27+
type MCPResourceTemplate,
28+
type MCPResourceContent,
2629
clientConfigSchema,
2730
connectionSchema,
2831
type LoadMcpToolsOptions,
@@ -391,6 +394,211 @@ export class MultiServerMCPClient {
391394
});
392395
}
393396

397+
/**
398+
* List resources from specified servers.
399+
*
400+
* @param servers - Optional array of server names to filter resources by.
401+
* If not provided, returns resources from all servers.
402+
* @param options - Optional connection options for the resource listing, e.g. custom auth provider or headers.
403+
* @returns A map of server names to their resources
404+
*
405+
* @example
406+
* ```ts
407+
* // List resources from all servers
408+
* const resources = await client.listResources();
409+
* ```
410+
*
411+
* @example
412+
* ```ts
413+
* // List resources from specific servers
414+
* const resources = await client.listResources("server1", "server2");
415+
* ```
416+
*/
417+
async listResources(
418+
...servers: string[]
419+
): Promise<Record<string, MCPResource[]>>;
420+
async listResources(
421+
servers: string[],
422+
options?: CustomHTTPTransportOptions
423+
): Promise<Record<string, MCPResource[]>>;
424+
async listResources(
425+
...args: unknown[]
426+
): Promise<Record<string, MCPResource[]>> {
427+
let servers: string[];
428+
let options: CustomHTTPTransportOptions | undefined;
429+
430+
if (args.length === 0 || args.every((arg) => typeof arg === "string")) {
431+
servers = args as string[];
432+
await this.initializeConnections();
433+
} else {
434+
[servers, options] = args as [
435+
string[],
436+
CustomHTTPTransportOptions | undefined,
437+
];
438+
await this.initializeConnections(options);
439+
}
440+
441+
const targetServers =
442+
servers.length > 0 ? servers : Object.keys(this.#config.mcpServers);
443+
444+
const result: Record<string, MCPResource[]> = {};
445+
446+
for (const serverName of targetServers) {
447+
const client = await this.getClient(serverName, options);
448+
if (!client) {
449+
debugLog(`WARN: Server "${serverName}" not found or not connected`);
450+
continue;
451+
}
452+
453+
try {
454+
const resourcesList = await client.listResources();
455+
result[serverName] = resourcesList.resources.map((resource) => ({
456+
uri: resource.uri,
457+
name: resource.title ?? resource.name,
458+
description: resource.description,
459+
mimeType: resource.mimeType,
460+
}));
461+
debugLog(
462+
`INFO: Listed ${result[serverName].length} resources from server "${serverName}"`
463+
);
464+
} catch (error) {
465+
debugLog(
466+
`ERROR: Failed to list resources from server "${serverName}": ${error}`
467+
);
468+
result[serverName] = [];
469+
}
470+
}
471+
472+
return result;
473+
}
474+
475+
/**
476+
* List resource templates from specified servers.
477+
*
478+
* Resource templates are used for dynamic resources with parameterized URIs.
479+
*
480+
* @param servers - Optional array of server names to filter resource templates by.
481+
* If not provided, returns resource templates from all servers.
482+
* @param options - Optional connection options for the resource template listing, e.g. custom auth provider or headers.
483+
* @returns A map of server names to their resource templates
484+
*
485+
* @example
486+
* ```ts
487+
* // List resource templates from all servers
488+
* const templates = await client.listResourceTemplates();
489+
* ```
490+
*
491+
* @example
492+
* ```ts
493+
* // List resource templates from specific servers
494+
* const templates = await client.listResourceTemplates("server1", "server2");
495+
* ```
496+
*/
497+
async listResourceTemplates(
498+
...servers: string[]
499+
): Promise<Record<string, MCPResourceTemplate[]>>;
500+
async listResourceTemplates(
501+
servers: string[],
502+
options?: CustomHTTPTransportOptions
503+
): Promise<Record<string, MCPResourceTemplate[]>>;
504+
async listResourceTemplates(
505+
...args: unknown[]
506+
): Promise<Record<string, MCPResourceTemplate[]>> {
507+
let servers: string[];
508+
let options: CustomHTTPTransportOptions | undefined;
509+
510+
if (args.length === 0 || args.every((arg) => typeof arg === "string")) {
511+
servers = args as string[];
512+
await this.initializeConnections();
513+
} else {
514+
[servers, options] = args as [
515+
string[],
516+
CustomHTTPTransportOptions | undefined,
517+
];
518+
await this.initializeConnections(options);
519+
}
520+
521+
const targetServers =
522+
servers.length > 0 ? servers : Object.keys(this.#config.mcpServers);
523+
524+
const result: Record<string, MCPResourceTemplate[]> = {};
525+
526+
for (const serverName of targetServers) {
527+
const client = await this.getClient(serverName, options);
528+
if (!client) {
529+
debugLog(`WARN: Server "${serverName}" not found or not connected`);
530+
continue;
531+
}
532+
533+
try {
534+
const templatesList = await client.listResourceTemplates();
535+
result[serverName] = templatesList.resourceTemplates.map(
536+
(template) => ({
537+
uriTemplate: template.uriTemplate,
538+
name: template.title ?? template.name,
539+
description: template.description,
540+
mimeType: template.mimeType,
541+
})
542+
);
543+
debugLog(
544+
`INFO: Listed ${result[serverName].length} resource templates from server "${serverName}"`
545+
);
546+
} catch (error) {
547+
debugLog(
548+
`ERROR: Failed to list resource templates from server "${serverName}": ${error}`
549+
);
550+
result[serverName] = [];
551+
}
552+
}
553+
554+
return result;
555+
}
556+
557+
/**
558+
* Read a resource from a specific server.
559+
*
560+
* @param serverName - The name of the server to read the resource from
561+
* @param uri - The URI of the resource to read
562+
* @param options - Optional connection options for reading the resource, e.g. custom auth provider or headers.
563+
* @returns The resource contents
564+
*
565+
* @example
566+
* ```ts
567+
* const content = await client.readResource("server1", "file://path/to/resource");
568+
* ```
569+
*/
570+
async readResource(
571+
serverName: string,
572+
uri: string,
573+
options?: CustomHTTPTransportOptions
574+
): Promise<MCPResourceContent[]> {
575+
await this.initializeConnections(options);
576+
577+
const client = await this.getClient(serverName, options);
578+
if (!client) {
579+
throw new MCPClientError(
580+
`Server "${serverName}" not found or not connected`,
581+
serverName
582+
);
583+
}
584+
585+
try {
586+
debugLog(`INFO: Reading resource "${uri}" from server "${serverName}"`);
587+
const result = await client.readResource({ uri });
588+
return result.contents.map((content) => ({
589+
uri: content.uri,
590+
mimeType: content.mimeType,
591+
text: content.text as string | undefined,
592+
blob: content.blob as string | undefined,
593+
}));
594+
} catch (error) {
595+
throw new MCPClientError(
596+
`Failed to read resource "${uri}" from server "${serverName}": ${error}`,
597+
serverName
598+
);
599+
}
600+
}
601+
394602
/**
395603
* Close all connections.
396604
*/

libs/langchain-mcp-adapters/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ export type {
99
OutputHandling,
1010
StdioConnection,
1111
StreamableHTTPConnection,
12+
MCPResource,
13+
MCPResourceTemplate,
14+
MCPResourceContent,
1215
} from "./types.js";
1316

1417
/**

0 commit comments

Comments
 (0)