Skip to content
This repository was archived by the owner on May 21, 2026. It is now read-only.

Commit 1dbe763

Browse files
committed
feat: enhance resource handling and add pagination support
- Updated resource definition in `server.ts` to include a title and improved description. - Enhanced `listResources` method in `base.ts` to support optional pagination with a cursor. - Introduced `listAllResources` method for automatic pagination of resource listings. - Added `subscribeToResource` and `unsubscribeFromResource` methods for managing resource updates. - Improved documentation for resource templates and annotations in `mcp-server.ts` and `types.ts`.
1 parent a25db00 commit 1dbe763

4 files changed

Lines changed: 264 additions & 24 deletions

File tree

packages/mcp-use/examples/server/simple/src/server.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,21 @@ server.tool({
3636
server.resource({
3737
name: 'test',
3838
uri: 'resource://test',
39+
title: 'Test Resource',
3940
mimeType: 'text/plain',
40-
description: 'A test resource',
41+
description: 'A test resource that returns a simple greeting',
42+
annotations: {
43+
audience: ['user', 'assistant'],
44+
priority: 0.5
45+
},
4146
fn: async () => {
42-
return 'ciao'
47+
return {
48+
contents: [{
49+
uri: 'resource://test',
50+
mimeType: 'text/plain',
51+
text: 'ciao'
52+
}]
53+
}
4354
}
4455
})
4556

packages/mcp-use/src/connectors/base.ts

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,59 @@ export abstract class BaseConnector {
9999
return res as CallToolResult
100100
}
101101

102-
/** List resources from the server. */
103-
async listResources(options?: RequestOptions) {
102+
/**
103+
* List resources from the server with optional pagination
104+
*
105+
* @param cursor - Optional cursor for pagination
106+
* @param options - Request options
107+
* @returns Resource list with optional nextCursor for pagination
108+
*/
109+
async listResources(cursor?: string, options?: RequestOptions) {
110+
if (!this.client) {
111+
throw new Error('MCP client is not connected')
112+
}
113+
114+
logger.debug('Listing resources', cursor ? `with cursor: ${cursor}` : '')
115+
return await this.client.listResources({ cursor }, options)
116+
}
117+
118+
/**
119+
* List all resources from the server, automatically handling pagination
120+
*
121+
* @param options - Request options
122+
* @returns Complete list of all resources
123+
*/
124+
async listAllResources(options?: RequestOptions) {
125+
if (!this.client) {
126+
throw new Error('MCP client is not connected')
127+
}
128+
129+
logger.debug('Listing all resources (with auto-pagination)')
130+
const allResources: any[] = []
131+
let cursor: string | undefined = undefined
132+
133+
do {
134+
const result = await this.client.listResources({ cursor }, options)
135+
allResources.push(...(result.resources || []))
136+
cursor = result.nextCursor
137+
} while (cursor)
138+
139+
return { resources: allResources }
140+
}
141+
142+
/**
143+
* List resource templates from the server
144+
*
145+
* @param options - Request options
146+
* @returns List of available resource templates
147+
*/
148+
async listResourceTemplates(options?: RequestOptions) {
104149
if (!this.client) {
105150
throw new Error('MCP client is not connected')
106151
}
107152

108-
logger.debug('Listing resources')
109-
return await this.client.listResources(undefined, options)
153+
logger.debug('Listing resource templates')
154+
return await this.client.listResourceTemplates(undefined, options)
110155
}
111156

112157
/** Read a resource by URI. */
@@ -120,6 +165,36 @@ export abstract class BaseConnector {
120165
return { content: res.content, mimeType: res.mimeType }
121166
}
122167

168+
/**
169+
* Subscribe to resource updates
170+
*
171+
* @param uri - URI of the resource to subscribe to
172+
* @param options - Request options
173+
*/
174+
async subscribeToResource(uri: string, options?: RequestOptions) {
175+
if (!this.client) {
176+
throw new Error('MCP client is not connected')
177+
}
178+
179+
logger.debug(`Subscribing to resource: ${uri}`)
180+
return await this.client.subscribeResource({ uri }, options)
181+
}
182+
183+
/**
184+
* Unsubscribe from resource updates
185+
*
186+
* @param uri - URI of the resource to unsubscribe from
187+
* @param options - Request options
188+
*/
189+
async unsubscribeFromResource(uri: string, options?: RequestOptions) {
190+
if (!this.client) {
191+
throw new Error('MCP client is not connected')
192+
}
193+
194+
logger.debug(`Unsubscribing from resource: ${uri}`)
195+
return await this.client.unsubscribeResource({ uri }, options)
196+
}
197+
123198
async listPrompts() {
124199
if (!this.client) {
125200
throw new Error('MCP client is not connected')

packages/mcp-use/src/server/mcp-server.ts

Lines changed: 141 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import type {
22
PromptDefinition,
33
ResourceDefinition,
4+
ResourceTemplateDefinition,
45
ServerConfig,
56
ToolDefinition,
67
} from './types.js'
7-
import { McpServer as OfficialMcpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
8+
import { McpServer as OfficialMcpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
89
import { z } from 'zod'
910
import express, { type Express } from 'express'
1011
import { existsSync, readdirSync } from 'node:fs'
@@ -77,7 +78,10 @@ export class McpServer {
7778
* @param resourceDefinition - Configuration object containing resource metadata and handler function
7879
* @param resourceDefinition.name - Unique identifier for the resource
7980
* @param resourceDefinition.uri - URI pattern for accessing the resource
80-
* @param resourceDefinition.resource - Resource metadata (mime type, description, etc.)
81+
* @param resourceDefinition.title - Optional human-readable title for the resource
82+
* @param resourceDefinition.description - Optional description of the resource
83+
* @param resourceDefinition.mimeType - MIME type of the resource content
84+
* @param resourceDefinition.annotations - Optional annotations (audience, priority, lastModified)
8185
* @param resourceDefinition.fn - Async function that returns the resource content
8286
* @returns The server instance for method chaining
8387
*
@@ -86,16 +90,34 @@ export class McpServer {
8690
* server.resource({
8791
* name: 'config',
8892
* uri: 'config://app-settings',
89-
* resource: { mimeType: 'application/json' },
90-
* fn: async () => ({ theme: 'dark', language: 'en' })
93+
* title: 'Application Settings',
94+
* mimeType: 'application/json',
95+
* description: 'Current application configuration',
96+
* annotations: {
97+
* audience: ['user'],
98+
* priority: 0.8
99+
* },
100+
* fn: async () => ({
101+
* contents: [{
102+
* uri: 'config://app-settings',
103+
* mimeType: 'application/json',
104+
* text: JSON.stringify({ theme: 'dark', language: 'en' })
105+
* }]
106+
* })
91107
* })
92108
* ```
93109
*/
94110
resource(resourceDefinition: ResourceDefinition): this {
95111
this.server.resource(
96112
resourceDefinition.name,
97113
resourceDefinition.uri,
98-
{mimeType: resourceDefinition.mimeType, description: resourceDefinition.description},
114+
{
115+
name: resourceDefinition.name,
116+
title: resourceDefinition.title,
117+
description: resourceDefinition.description,
118+
mimeType: resourceDefinition.mimeType,
119+
annotations: resourceDefinition.annotations,
120+
},
99121
async () => {
100122
return await resourceDefinition.fn()
101123
},
@@ -105,18 +127,79 @@ export class McpServer {
105127

106128
/**
107129
* Define a dynamic resource template with parameters
130+
*
131+
* Registers a parameterized resource template with the MCP server. Templates use URI
132+
* patterns with placeholders that can be filled in at request time, allowing dynamic
133+
* resource generation based on parameters.
134+
*
135+
* @param resourceTemplateDefinition - Configuration object for the resource template
136+
* @param resourceTemplateDefinition.name - Unique identifier for the template
137+
* @param resourceTemplateDefinition.resourceTemplate - ResourceTemplate object with uriTemplate and metadata
138+
* @param resourceTemplateDefinition.fn - Async function that generates resource content from URI and params
139+
* @returns The server instance for method chaining
140+
*
141+
* @example
142+
* ```typescript
143+
* server.resourceTemplate({
144+
* name: 'user-profile',
145+
* resourceTemplate: {
146+
* uriTemplate: 'user://{userId}/profile',
147+
* name: 'User Profile',
148+
* mimeType: 'application/json'
149+
* },
150+
* fn: async (uri, params) => ({
151+
* contents: [{
152+
* uri: uri.toString(),
153+
* mimeType: 'application/json',
154+
* text: JSON.stringify({ userId: params.userId, name: 'John Doe' })
155+
* }]
156+
* })
157+
* })
158+
* ```
108159
*/
109-
// TODO implement, for some freaky reason this give errors
110-
// resourceTemplate(resourceTemplateDefinition: ResourceTemplateDefinition): this {
111-
// this.server.resource(
112-
// resourceTemplateDefinition.name,
113-
// resourceTemplateDefinition.resourceTemplate,
114-
// async (uri, params) => {
115-
// return await resourceTemplateDefinition.fn(uri, params)
116-
// },
117-
// )
118-
// return this
119-
// }
160+
resourceTemplate(resourceTemplateDefinition: ResourceTemplateDefinition): this {
161+
// Create ResourceTemplate instance from SDK
162+
const template = new ResourceTemplate(
163+
resourceTemplateDefinition.resourceTemplate.uriTemplate,
164+
{
165+
list: undefined, // Optional: callback to list all matching resources
166+
complete: undefined // Optional: callback for auto-completion
167+
}
168+
)
169+
170+
// Create metadata object with optional fields
171+
const metadata: any = {}
172+
if (resourceTemplateDefinition.resourceTemplate.name) {
173+
metadata.name = resourceTemplateDefinition.resourceTemplate.name
174+
}
175+
if (resourceTemplateDefinition.title) {
176+
metadata.title = resourceTemplateDefinition.title
177+
}
178+
if (resourceTemplateDefinition.description || resourceTemplateDefinition.resourceTemplate.description) {
179+
metadata.description = resourceTemplateDefinition.description || resourceTemplateDefinition.resourceTemplate.description
180+
}
181+
if (resourceTemplateDefinition.resourceTemplate.mimeType) {
182+
metadata.mimeType = resourceTemplateDefinition.resourceTemplate.mimeType
183+
}
184+
if (resourceTemplateDefinition.annotations) {
185+
metadata.annotations = resourceTemplateDefinition.annotations
186+
}
187+
188+
this.server.resource(
189+
resourceTemplateDefinition.name,
190+
template,
191+
metadata,
192+
async (uri: URL) => {
193+
// Parse URI parameters from the template
194+
const params = this.parseTemplateUri(
195+
resourceTemplateDefinition.resourceTemplate.uriTemplate,
196+
uri.toString()
197+
)
198+
return await resourceTemplateDefinition.fn(uri, params)
199+
},
200+
)
201+
return this
202+
}
120203

121204
/**
122205
* Define a tool that can be called by clients
@@ -552,6 +635,48 @@ export class McpServer {
552635
const matches = uriTemplate.match(/\{([^}]+)\}/g)
553636
return matches ? matches.map(match => match.slice(1, -1)) : []
554637
}
638+
639+
/**
640+
* Parse parameter values from a URI based on a template
641+
*
642+
* Extracts parameter values from an actual URI by matching it against a URI template.
643+
* The template contains placeholders like {param} which are extracted as key-value pairs.
644+
*
645+
* @param template - URI template with placeholders (e.g., "user://{userId}/posts/{postId}")
646+
* @param uri - Actual URI to parse (e.g., "user://123/posts/456")
647+
* @returns Object mapping parameter names to their values
648+
*
649+
* @example
650+
* ```typescript
651+
* const params = this.parseTemplateUri("user://{userId}/posts/{postId}", "user://123/posts/456")
652+
* // Returns: { userId: "123", postId: "456" }
653+
* ```
654+
*/
655+
private parseTemplateUri(template: string, uri: string): Record<string, string> {
656+
const params: Record<string, string> = {}
657+
658+
// Convert template to a regex pattern
659+
// Escape special regex characters except {}
660+
let regexPattern = template.replace(/[.*+?^$()[\]\\|]/g, '\\$&')
661+
662+
// Replace {param} with named capture groups
663+
const paramNames: string[] = []
664+
regexPattern = regexPattern.replace(/\\\{([^}]+)\\\}/g, (_, paramName) => {
665+
paramNames.push(paramName)
666+
return '([^/]+)'
667+
})
668+
669+
const regex = new RegExp(`^${regexPattern}$`)
670+
const match = uri.match(regex)
671+
672+
if (match) {
673+
paramNames.forEach((paramName, index) => {
674+
params[paramName] = match[index + 1]
675+
})
676+
}
677+
678+
return params
679+
}
555680
}
556681

557682
export type McpServerInstance = Omit<McpServer, keyof Express> & Express

packages/mcp-use/src/server/types.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { CallToolResult, GetPromptResult, ReadResourceResult, ResourceTemplate} from '@modelcontextprotocol/sdk/types.js'
1+
import type { CallToolResult, GetPromptResult, ReadResourceResult} from '@modelcontextprotocol/sdk/types.js'
22
export interface ServerConfig {
33
name: string
44
version: string
@@ -13,11 +13,38 @@ export interface InputDefinition {
1313
default?: any
1414
}
1515

16+
/**
17+
* Annotations provide hints to clients about how to use or display resources
18+
*/
19+
export interface ResourceAnnotations {
20+
/** Intended audience(s) for this resource */
21+
audience?: ('user' | 'assistant')[]
22+
/** Priority from 0.0 (least important) to 1.0 (most important) */
23+
priority?: number
24+
/** ISO 8601 formatted timestamp of last modification */
25+
lastModified?: string
26+
}
27+
28+
/**
29+
* Configuration for a resource template
30+
*/
31+
export interface ResourceTemplateConfig {
32+
/** URI template with {param} placeholders (e.g., "user://{userId}/profile") */
33+
uriTemplate: string
34+
/** Name of the resource */
35+
name?: string
36+
/** MIME type of the resource content */
37+
mimeType?: string
38+
/** Description of the resource */
39+
description?: string
40+
}
41+
1642
export interface ResourceTemplateDefinition {
1743
name: string
18-
resourceTemplate: ResourceTemplate
44+
resourceTemplate: ResourceTemplateConfig
1945
title?: string
2046
description?: string
47+
annotations?: ResourceAnnotations
2148
fn: ResourceTemplateHandler
2249
}
2350

@@ -33,6 +60,8 @@ export interface ResourceDefinition {
3360
description?: string
3461
/** MIME type of the resource content (required) */
3562
mimeType: string
63+
/** Optional annotations for the resource */
64+
annotations?: ResourceAnnotations
3665
/** Async function that returns the resource content */
3766
fn: ResourceHandler
3867
}

0 commit comments

Comments
 (0)