11import 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'
89import { z } from 'zod'
910import express , { type Express } from 'express'
1011import { 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
557682export type McpServerInstance = Omit < McpServer , keyof Express > & Express
0 commit comments