Skip to content

Commit ff98e29

Browse files
committed
refactor: Improve resource template support (#576)
- Provide separation of concerns between static resources and parameterized resource templates. - Add AsyncResourceTemplateSpecification and SyncResourceTemplateSpecification for both McpServerFeatures and McpStatelessServerFeatures - Change resource template storage from List to Map to acomodate the resource read handler. - Add runtime management methods: addResourceTemplate(), removeResourceTemplate(), listResourceTemplates() - Improve error handling by using IllegalArgumentException/IllegalStateException instead of McpError - Add new interfaces (Meta, Identifier) and reorganize schema hierarchy - Enhance completion request validation with better error messages - Add ResourceTemplate.Builder for easier template construction - Update all server implementations (Async, Sync, Stateless) consistently - Add type-safe constants for reference types (PromptReference.TYPE, ResourceReference.TYPE) - Add tests for new resource template management functionality - Clean up imports and remove unused dependencies Co-authored-by: Pascal Vantrepote <[email protected]> Signed-off-by: Christian Tzolov <[email protected]>
1 parent 7f16cd0 commit ff98e29

20 files changed

+1957
-386
lines changed

mcp-core/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java

Lines changed: 198 additions & 68 deletions
Large diffs are not rendered by default.

mcp-core/src/main/java/io/modelcontextprotocol/server/McpServer.java

Lines changed: 83 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,14 @@ abstract class AsyncSpecification<S extends AsyncSpecification<S>> {
298298
*/
299299
final Map<String, McpServerFeatures.AsyncResourceSpecification> resources = new HashMap<>();
300300

301-
final List<ResourceTemplate> resourceTemplates = new ArrayList<>();
301+
/**
302+
* The Model Context Protocol (MCP) provides a standardized way for servers to
303+
* expose resource templates to clients. Resource templates allow servers to
304+
* define parameterized URIs that clients can use to access dynamic resources.
305+
* Each resource template includes variables that clients can fill in to form
306+
* concrete resource URIs.
307+
*/
308+
final Map<String, McpServerFeatures.AsyncResourceTemplateSpecification> resourceTemplates = new HashMap<>();
302309

303310
/**
304311
* The Model Context Protocol (MCP) provides a standardized way for servers to
@@ -585,40 +592,39 @@ public AsyncSpecification<S> resources(McpServerFeatures.AsyncResourceSpecificat
585592
}
586593

587594
/**
588-
* Sets the resource templates that define patterns for dynamic resource access.
589-
* Templates use URI patterns with placeholders that can be filled at runtime.
590-
*
591-
* <p>
592-
* Example usage: <pre>{@code
593-
* .resourceTemplates(
594-
* new ResourceTemplate("file://{path}", "Access files by path"),
595-
* new ResourceTemplate("db://{table}/{id}", "Access database records")
596-
* )
597-
* }</pre>
598-
* @param resourceTemplates List of resource templates. If null, clears existing
599-
* templates.
595+
* Registers multiple resource templates with their specifications using a List.
596+
* This method is useful when resource templates need to be added in bulk from a
597+
* collection.
598+
* @param resourceTemplates Map of template URI to specification. Must not be
599+
* null.
600600
* @return This builder instance for method chaining
601601
* @throws IllegalArgumentException if resourceTemplates is null.
602602
* @see #resourceTemplates(ResourceTemplate...)
603603
*/
604-
public AsyncSpecification<S> resourceTemplates(List<ResourceTemplate> resourceTemplates) {
604+
public AsyncSpecification<S> resourceTemplates(
605+
List<McpServerFeatures.AsyncResourceTemplateSpecification> resourceTemplates) {
605606
Assert.notNull(resourceTemplates, "Resource templates must not be null");
606-
this.resourceTemplates.addAll(resourceTemplates);
607+
for (var resourceTemplate : resourceTemplates) {
608+
this.resourceTemplates.put(resourceTemplate.resourceTemplate().uriTemplate(), resourceTemplate);
609+
}
607610
return this;
608611
}
609612

610613
/**
611-
* Sets the resource templates using varargs for convenience. This is an
612-
* alternative to {@link #resourceTemplates(List)}.
613-
* @param resourceTemplates The resource templates to set.
614+
* Registers multiple resource templates with their specifications using a List.
615+
* This method is useful when resource templates need to be added in bulk from a
616+
* collection.
617+
* @param resourceTemplates List of template URI to specification. Must not be
618+
* null.
614619
* @return This builder instance for method chaining
615620
* @throws IllegalArgumentException if resourceTemplates is null.
616621
* @see #resourceTemplates(List)
617622
*/
618-
public AsyncSpecification<S> resourceTemplates(ResourceTemplate... resourceTemplates) {
623+
public AsyncSpecification<S> resourceTemplates(
624+
McpServerFeatures.AsyncResourceTemplateSpecification... resourceTemplates) {
619625
Assert.notNull(resourceTemplates, "Resource templates must not be null");
620-
for (ResourceTemplate resourceTemplate : resourceTemplates) {
621-
this.resourceTemplates.add(resourceTemplate);
626+
for (McpServerFeatures.AsyncResourceTemplateSpecification resource : resourceTemplates) {
627+
this.resourceTemplates.put(resource.resourceTemplate().uriTemplate(), resource);
622628
}
623629
return this;
624630
}
@@ -887,7 +893,14 @@ abstract class SyncSpecification<S extends SyncSpecification<S>> {
887893
*/
888894
final Map<String, McpServerFeatures.SyncResourceSpecification> resources = new HashMap<>();
889895

890-
final List<ResourceTemplate> resourceTemplates = new ArrayList<>();
896+
/**
897+
* The Model Context Protocol (MCP) provides a standardized way for servers to
898+
* expose resource templates to clients. Resource templates allow servers to
899+
* define parameterized URIs that clients can use to access dynamic resources.
900+
* Each resource template includes variables that clients can fill in to form
901+
* concrete resource URIs.
902+
*/
903+
final Map<String, McpServerFeatures.SyncResourceTemplateSpecification> resourceTemplates = new HashMap<>();
891904

892905
JsonSchemaValidator jsonSchemaValidator;
893906

@@ -1179,23 +1192,18 @@ public SyncSpecification<S> resources(McpServerFeatures.SyncResourceSpecificatio
11791192
/**
11801193
* Sets the resource templates that define patterns for dynamic resource access.
11811194
* Templates use URI patterns with placeholders that can be filled at runtime.
1182-
*
1183-
* <p>
1184-
* Example usage: <pre>{@code
1185-
* .resourceTemplates(
1186-
* new ResourceTemplate("file://{path}", "Access files by path"),
1187-
* new ResourceTemplate("db://{table}/{id}", "Access database records")
1188-
* )
1189-
* }</pre>
1190-
* @param resourceTemplates List of resource templates. If null, clears existing
1191-
* templates.
1195+
* @param resourceTemplates List of resource template specifications. Must not be
1196+
* null.
11921197
* @return This builder instance for method chaining
11931198
* @throws IllegalArgumentException if resourceTemplates is null.
11941199
* @see #resourceTemplates(ResourceTemplate...)
11951200
*/
1196-
public SyncSpecification<S> resourceTemplates(List<ResourceTemplate> resourceTemplates) {
1201+
public SyncSpecification<S> resourceTemplates(
1202+
List<McpServerFeatures.SyncResourceTemplateSpecification> resourceTemplates) {
11971203
Assert.notNull(resourceTemplates, "Resource templates must not be null");
1198-
this.resourceTemplates.addAll(resourceTemplates);
1204+
for (McpServerFeatures.SyncResourceTemplateSpecification resource : resourceTemplates) {
1205+
this.resourceTemplates.put(resource.resourceTemplate().uriTemplate(), resource);
1206+
}
11991207
return this;
12001208
}
12011209

@@ -1207,10 +1215,11 @@ public SyncSpecification<S> resourceTemplates(List<ResourceTemplate> resourceTem
12071215
* @throws IllegalArgumentException if resourceTemplates is null
12081216
* @see #resourceTemplates(List)
12091217
*/
1210-
public SyncSpecification<S> resourceTemplates(ResourceTemplate... resourceTemplates) {
1218+
public SyncSpecification<S> resourceTemplates(
1219+
McpServerFeatures.SyncResourceTemplateSpecification... resourceTemplates) {
12111220
Assert.notNull(resourceTemplates, "Resource templates must not be null");
1212-
for (ResourceTemplate resourceTemplate : resourceTemplates) {
1213-
this.resourceTemplates.add(resourceTemplate);
1221+
for (McpServerFeatures.SyncResourceTemplateSpecification resourceTemplate : resourceTemplates) {
1222+
this.resourceTemplates.put(resourceTemplate.resourceTemplate().uriTemplate(), resourceTemplate);
12141223
}
12151224
return this;
12161225
}
@@ -1428,7 +1437,14 @@ class StatelessAsyncSpecification {
14281437
*/
14291438
final Map<String, McpStatelessServerFeatures.AsyncResourceSpecification> resources = new HashMap<>();
14301439

1431-
final List<ResourceTemplate> resourceTemplates = new ArrayList<>();
1440+
/**
1441+
* The Model Context Protocol (MCP) provides a standardized way for servers to
1442+
* expose resource templates to clients. Resource templates allow servers to
1443+
* define parameterized URIs that clients can use to access dynamic resources.
1444+
* Each resource template includes variables that clients can fill in to form
1445+
* concrete resource URIs.
1446+
*/
1447+
final Map<String, McpStatelessServerFeatures.AsyncResourceTemplateSpecification> resourceTemplates = new HashMap<>();
14321448

14331449
/**
14341450
* The Model Context Protocol (MCP) provides a standardized way for servers to
@@ -1684,23 +1700,18 @@ public StatelessAsyncSpecification resources(
16841700
/**
16851701
* Sets the resource templates that define patterns for dynamic resource access.
16861702
* Templates use URI patterns with placeholders that can be filled at runtime.
1687-
*
1688-
* <p>
1689-
* Example usage: <pre>{@code
1690-
* .resourceTemplates(
1691-
* new ResourceTemplate("file://{path}", "Access files by path"),
1692-
* new ResourceTemplate("db://{table}/{id}", "Access database records")
1693-
* )
1694-
* }</pre>
16951703
* @param resourceTemplates List of resource templates. If null, clears existing
16961704
* templates.
16971705
* @return This builder instance for method chaining
16981706
* @throws IllegalArgumentException if resourceTemplates is null.
16991707
* @see #resourceTemplates(ResourceTemplate...)
17001708
*/
1701-
public StatelessAsyncSpecification resourceTemplates(List<ResourceTemplate> resourceTemplates) {
1709+
public StatelessAsyncSpecification resourceTemplates(
1710+
List<McpStatelessServerFeatures.AsyncResourceTemplateSpecification> resourceTemplates) {
17021711
Assert.notNull(resourceTemplates, "Resource templates must not be null");
1703-
this.resourceTemplates.addAll(resourceTemplates);
1712+
for (var resourceTemplate : resourceTemplates) {
1713+
this.resourceTemplates.put(resourceTemplate.resourceTemplate().uriTemplate(), resourceTemplate);
1714+
}
17041715
return this;
17051716
}
17061717

@@ -1712,10 +1723,11 @@ public StatelessAsyncSpecification resourceTemplates(List<ResourceTemplate> reso
17121723
* @throws IllegalArgumentException if resourceTemplates is null.
17131724
* @see #resourceTemplates(List)
17141725
*/
1715-
public StatelessAsyncSpecification resourceTemplates(ResourceTemplate... resourceTemplates) {
1726+
public StatelessAsyncSpecification resourceTemplates(
1727+
McpStatelessServerFeatures.AsyncResourceTemplateSpecification... resourceTemplates) {
17161728
Assert.notNull(resourceTemplates, "Resource templates must not be null");
1717-
for (ResourceTemplate resourceTemplate : resourceTemplates) {
1718-
this.resourceTemplates.add(resourceTemplate);
1729+
for (McpStatelessServerFeatures.AsyncResourceTemplateSpecification resourceTemplate : resourceTemplates) {
1730+
this.resourceTemplates.put(resourceTemplate.resourceTemplate().uriTemplate(), resourceTemplate);
17191731
}
17201732
return this;
17211733
}
@@ -1888,7 +1900,14 @@ class StatelessSyncSpecification {
18881900
*/
18891901
final Map<String, McpStatelessServerFeatures.SyncResourceSpecification> resources = new HashMap<>();
18901902

1891-
final List<ResourceTemplate> resourceTemplates = new ArrayList<>();
1903+
/**
1904+
* The Model Context Protocol (MCP) provides a standardized way for servers to
1905+
* expose resource templates to clients. Resource templates allow servers to
1906+
* define parameterized URIs that clients can use to access dynamic resources.
1907+
* Each resource template includes variables that clients can fill in to form
1908+
* concrete resource URIs.
1909+
*/
1910+
final Map<String, McpStatelessServerFeatures.SyncResourceTemplateSpecification> resourceTemplates = new HashMap<>();
18921911

18931912
/**
18941913
* The Model Context Protocol (MCP) provides a standardized way for servers to
@@ -2144,23 +2163,18 @@ public StatelessSyncSpecification resources(
21442163
/**
21452164
* Sets the resource templates that define patterns for dynamic resource access.
21462165
* Templates use URI patterns with placeholders that can be filled at runtime.
2147-
*
2148-
* <p>
2149-
* Example usage: <pre>{@code
2150-
* .resourceTemplates(
2151-
* new ResourceTemplate("file://{path}", "Access files by path"),
2152-
* new ResourceTemplate("db://{table}/{id}", "Access database records")
2153-
* )
2154-
* }</pre>
2155-
* @param resourceTemplates List of resource templates. If null, clears existing
2156-
* templates.
2166+
* @param resourceTemplatesSpec List of resource templates. If null, clears
2167+
* existing templates.
21572168
* @return This builder instance for method chaining
21582169
* @throws IllegalArgumentException if resourceTemplates is null.
21592170
* @see #resourceTemplates(ResourceTemplate...)
21602171
*/
2161-
public StatelessSyncSpecification resourceTemplates(List<ResourceTemplate> resourceTemplates) {
2162-
Assert.notNull(resourceTemplates, "Resource templates must not be null");
2163-
this.resourceTemplates.addAll(resourceTemplates);
2172+
public StatelessSyncSpecification resourceTemplates(
2173+
List<McpStatelessServerFeatures.SyncResourceTemplateSpecification> resourceTemplatesSpec) {
2174+
Assert.notNull(resourceTemplatesSpec, "Resource templates must not be null");
2175+
for (var resourceTemplate : resourceTemplatesSpec) {
2176+
this.resourceTemplates.put(resourceTemplate.resourceTemplate().uriTemplate(), resourceTemplate);
2177+
}
21642178
return this;
21652179
}
21662180

@@ -2172,10 +2186,11 @@ public StatelessSyncSpecification resourceTemplates(List<ResourceTemplate> resou
21722186
* @throws IllegalArgumentException if resourceTemplates is null.
21732187
* @see #resourceTemplates(List)
21742188
*/
2175-
public StatelessSyncSpecification resourceTemplates(ResourceTemplate... resourceTemplates) {
2189+
public StatelessSyncSpecification resourceTemplates(
2190+
McpStatelessServerFeatures.SyncResourceTemplateSpecification... resourceTemplates) {
21762191
Assert.notNull(resourceTemplates, "Resource templates must not be null");
2177-
for (ResourceTemplate resourceTemplate : resourceTemplates) {
2178-
this.resourceTemplates.add(resourceTemplate);
2192+
for (McpStatelessServerFeatures.SyncResourceTemplateSpecification resourceTemplate : resourceTemplates) {
2193+
this.resourceTemplates.put(resourceTemplate.resourceTemplate().uriTemplate(), resourceTemplate);
21792194
}
21802195
return this;
21812196
}

0 commit comments

Comments
 (0)