11import type { Tool } from '@modelcontextprotocol/server'
22import type { StandardSchemaV1 } from '@standard-schema/spec'
33import type { RpcFunctionDefinitionAnyWithContext } from 'devframe/rpc'
4- import type { AgentTool , CreateMcpServerOptions , DevframeDefinition , DevframeHost , DevframeNodeContext , McpContentBlock , McpResourcesChangedHook , McpResourceTemplate , McpServerHandle } from 'devframe/types'
4+ import type { AgentTool , CreateMcpServerOptions , DevframeDefinition , DevframeHost , DevframeNodeContext , McpContentBlock , McpResourcesChangedHook , McpResourceTemplate , McpResourceTemplateEntry , McpServerHandle } from 'devframe/types'
55import { homedir } from 'node:os'
66import process from 'node:process'
77import { Server } from '@modelcontextprotocol/server'
@@ -68,8 +68,11 @@ export function buildMcpServerFromContext(
6868 registerToolHandlers ( server , ctx , options . exposeSharedState )
6969 const templates = options . resourceTemplates ?? [ ]
7070 registerResourceHandlers ( server , ctx , options . exposeSharedState , templates )
71- if ( templates . length )
72- registerResourceTemplateHandlers ( server , templates )
71+ if ( templates . length ) {
72+ server . setRequestHandler ( 'resources/templates/list' , async ( ) => ( {
73+ resourceTemplates : templates . map ( ( { template : { uriTemplate, name, title, description, mimeType } } ) => ( { uriTemplate, name, title, description, mimeType } ) ) ,
74+ } ) )
75+ }
7376
7477 return server
7578}
@@ -341,7 +344,7 @@ function registerResourceHandlers(
341344 const stateFilter = sharedStateFilter ( exposeSharedState )
342345
343346 server . setRequestHandler ( 'resources/list' , async ( ) => {
344- const resources : ResourceListing [ ] = ctx . agent . list ( ) . resources . map ( resource => ( {
347+ const resources : McpResourceTemplateEntry [ ] = ctx . agent . list ( ) . resources . map ( resource => ( {
345348 uri : resource . uri ,
346349 name : resource . name ,
347350 description : resource . description ,
@@ -429,24 +432,8 @@ function registerResourceHandlers(
429432 } )
430433}
431434
432- /** One entry of the `resources/list` result. */
433- interface ResourceListing {
434- uri : string
435- name : string
436- title ?: string
437- description ?: string
438- mimeType ?: string
439- }
440-
441- /** Serve `resources/templates/list`: one entry per template, in the given order. */
442- function registerResourceTemplateHandlers ( server : Server , templates : CompiledResourceTemplate [ ] ) : void {
443- server . setRequestHandler ( 'resources/templates/list' , async ( ) => ( {
444- resourceTemplates : templates . map ( ( { template : { uriTemplate, name, title, description, mimeType } } ) => ( { uriTemplate, name, title, description, mimeType } ) ) ,
445- } ) )
446- }
447-
448435/** One `{name}` expression of a URI template. */
449- const TEMPLATE_EXPRESSION_RE = / \{ ( [ ^ { } ] * ) \} / g
436+ const TEMPLATE_EXPRESSION_RE = / \{ ( [ ^ { } ] * ) \} /
450437/** RFC 6570 `varname`: letters, digits and `_`, with single dots between them. */
451438const TEMPLATE_VARNAME_RE = / ^ \w + (?: \. \w + ) * $ /
452439
@@ -461,40 +448,25 @@ const TEMPLATE_VARNAME_RE = /^\w+(?:\.\w+)*$/
461448 * @internal
462449 */
463450export function compileResourceTemplates ( templates : McpResourceTemplate [ ] = [ ] ) : CompiledResourceTemplate [ ] {
464- return templates . map ( compileResourceTemplate )
465- }
466-
467- function compileResourceTemplate ( template : McpResourceTemplate ) : CompiledResourceTemplate {
468- const { name, uriTemplate } = template
469- const invalid = ( reason : string ) => diagnostics . DF0084 ( { name, uriTemplate, reason } )
470- const names : string [ ] = [ ]
471- // The text around the expressions: one more than `names`.
472- const literals : string [ ] = [ ]
473- let last = 0
474- for ( const expression of uriTemplate . matchAll ( TEMPLATE_EXPRESSION_RE ) ) {
475- const literal = uriTemplate . slice ( last , expression . index )
476- if ( / [ { } ] / . test ( literal ) )
451+ return templates . map ( ( template ) => {
452+ const { name, uriTemplate } = template
453+ const invalid = ( reason : string ) => diagnostics . DF0084 ( { name, uriTemplate, reason } )
454+ // The capture group keeps each name: literals at even indexes, names at odd ones.
455+ const parts = uriTemplate . split ( TEMPLATE_EXPRESSION_RE )
456+ const literals = parts . filter ( ( _ , index ) => index % 2 === 0 )
457+ const names = parts . filter ( ( _ , index ) => index % 2 === 1 )
458+ if ( literals . some ( literal => / [ { } ] / . test ( literal ) ) )
477459 throw invalid ( 'a `{` or `}` is outside an expression.' )
478- const varname = expression [ 1 ] !
479- if ( ! TEMPLATE_VARNAME_RE . test ( varname ) )
480- throw invalid ( `the expression "{${ varname } }" is not a plain \`{name}\`.` )
481- if ( names . includes ( varname ) )
482- throw invalid ( `the name "${ varname } " is used more than once.` )
483- if ( names . length > 0 && literal === '' )
484- throw invalid ( `"{${ names . at ( - 1 ) } }" and "{${ varname } }" have nothing between them, so a URI cannot show where one ends.` )
485- literals . push ( literal )
486- names . push ( varname )
487- last = expression . index + expression [ 0 ] . length
488- }
489- const tail = uriTemplate . slice ( last )
490- if ( / [ { } ] / . test ( tail ) )
491- throw invalid ( 'a `{` or `}` is outside an expression.' )
492- literals . push ( tail )
493-
494- return {
495- template,
496- match : uri => matchTemplate ( uri , literals , names ) ,
497- }
460+ names . forEach ( ( varname , index ) => {
461+ if ( ! TEMPLATE_VARNAME_RE . test ( varname ) )
462+ throw invalid ( `the expression "{${ varname } }" is not a plain \`{name}\`.` )
463+ if ( names . indexOf ( varname ) !== index )
464+ throw invalid ( `the name "${ varname } " is used more than once.` )
465+ if ( index > 0 && literals [ index ] === '' )
466+ throw invalid ( `"{${ names [ index - 1 ] } }" and "{${ varname } }" have nothing between them, so a URI cannot show where one ends.` )
467+ } )
468+ return { template, match : uri => matchTemplate ( uri , literals , names ) }
469+ } )
498470}
499471
500472/**
@@ -520,7 +492,7 @@ function matchTemplate(uri: string, literals: string[], names: string[]): Record
520492 const isLast = index === names . length - 1
521493 const next = literals [ index + 1 ] !
522494 const stop = isLast ? end : findLiteral ( uri , next , position )
523- if ( stop <= position || stop > end )
495+ if ( stop <= position )
524496 return undefined
525497 const raw = uri . slice ( position , stop )
526498 if ( raw . includes ( '/' ) )
0 commit comments