Skip to content

Commit 9126875

Browse files
committed
refactor: Improve resource template management and API consistency
- Convert resource templates from List to Map-based storage for better lookup performance - Add runtime resource template management methods (add/remove/list) - Replace McpError with more appropriate exception types (IllegalArgumentException, IllegalStateException) - Enhance completion request handling with better validation and error messages - Add type-safe constants for reference types (PromptReference.TYPE, ResourceReference.TYPE) - Improve separation between static resources and dynamic resource templates - Add comprehensive resource and resource template listing capabilities - Reorganize imports and improve code structure across server implementations - Update test cases to reflect new API patterns - Add tests for listing, removing, and managing resources - Add tests for resource template operations (add, remove, list) - Include capability validation tests for resource templates - Cover edge cases like removing nonexistent resources/templates - Apply changes to both async and sync server test suites - Reorganize imports for better code organization This refactoring improves type safety, performance, and maintainability while providing a more consistent API for resource template management across all server implementations (Async, Sync, Stateless variants). Signed-off-by: Christian Tzolov <[email protected]>
1 parent 7f16cd0 commit 9126875

20 files changed

+1890
-369
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: 76 additions & 67 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,37 @@ 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 resourceTemplatesSpec 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) {
605-
Assert.notNull(resourceTemplates, "Resource templates must not be null");
606-
this.resourceTemplates.addAll(resourceTemplates);
604+
public AsyncSpecification<S> resourceTemplates(
605+
Map<String, McpServerFeatures.AsyncResourceTemplateSpecification> resourceTemplatesSpec) {
606+
Assert.notNull(resourceTemplatesSpec, "Resource templates must not be null");
607+
this.resourceTemplates.putAll(resourceTemplatesSpec);
607608
return this;
608609
}
609610

610611
/**
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.
612+
* Registers multiple resource templates with their specifications using a List.
613+
* This method is useful when resource templates need to be added in bulk from a
614+
* collection.
615+
* @param resourceTemplatesSpec Map of template URI to specification. Must not be
616+
* null.
614617
* @return This builder instance for method chaining
615618
* @throws IllegalArgumentException if resourceTemplates is null.
616619
* @see #resourceTemplates(List)
617620
*/
618-
public AsyncSpecification<S> resourceTemplates(ResourceTemplate... resourceTemplates) {
619-
Assert.notNull(resourceTemplates, "Resource templates must not be null");
620-
for (ResourceTemplate resourceTemplate : resourceTemplates) {
621-
this.resourceTemplates.add(resourceTemplate);
621+
public AsyncSpecification<S> resourceTemplates(
622+
McpServerFeatures.AsyncResourceTemplateSpecification... resourceTemplatesSpec) {
623+
Assert.notNull(resourceTemplatesSpec, "Resource templates must not be null");
624+
for (McpServerFeatures.AsyncResourceTemplateSpecification resource : resourceTemplatesSpec) {
625+
this.resourceTemplates.put(resource.resourceTemplate().uriTemplate(), resource);
622626
}
623627
return this;
624628
}
@@ -887,7 +891,14 @@ abstract class SyncSpecification<S extends SyncSpecification<S>> {
887891
*/
888892
final Map<String, McpServerFeatures.SyncResourceSpecification> resources = new HashMap<>();
889893

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

892903
JsonSchemaValidator jsonSchemaValidator;
893904

@@ -1179,23 +1190,18 @@ public SyncSpecification<S> resources(McpServerFeatures.SyncResourceSpecificatio
11791190
/**
11801191
* Sets the resource templates that define patterns for dynamic resource access.
11811192
* 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.
1193+
* @param resourceTemplates List of resource template specifications. Must not be
1194+
* null.
11921195
* @return This builder instance for method chaining
11931196
* @throws IllegalArgumentException if resourceTemplates is null.
11941197
* @see #resourceTemplates(ResourceTemplate...)
11951198
*/
1196-
public SyncSpecification<S> resourceTemplates(List<ResourceTemplate> resourceTemplates) {
1199+
public SyncSpecification<S> resourceTemplates(
1200+
List<McpServerFeatures.SyncResourceTemplateSpecification> resourceTemplates) {
11971201
Assert.notNull(resourceTemplates, "Resource templates must not be null");
1198-
this.resourceTemplates.addAll(resourceTemplates);
1202+
for (McpServerFeatures.SyncResourceTemplateSpecification resource : resourceTemplates) {
1203+
this.resourceTemplates.put(resource.resourceTemplate().uriTemplate(), resource);
1204+
}
11991205
return this;
12001206
}
12011207

@@ -1207,10 +1213,11 @@ public SyncSpecification<S> resourceTemplates(List<ResourceTemplate> resourceTem
12071213
* @throws IllegalArgumentException if resourceTemplates is null
12081214
* @see #resourceTemplates(List)
12091215
*/
1210-
public SyncSpecification<S> resourceTemplates(ResourceTemplate... resourceTemplates) {
1216+
public SyncSpecification<S> resourceTemplates(
1217+
McpServerFeatures.SyncResourceTemplateSpecification... resourceTemplates) {
12111218
Assert.notNull(resourceTemplates, "Resource templates must not be null");
1212-
for (ResourceTemplate resourceTemplate : resourceTemplates) {
1213-
this.resourceTemplates.add(resourceTemplate);
1219+
for (McpServerFeatures.SyncResourceTemplateSpecification resourceTemplate : resourceTemplates) {
1220+
this.resourceTemplates.put(resourceTemplate.resourceTemplate().uriTemplate(), resourceTemplate);
12141221
}
12151222
return this;
12161223
}
@@ -1428,7 +1435,14 @@ class StatelessAsyncSpecification {
14281435
*/
14291436
final Map<String, McpStatelessServerFeatures.AsyncResourceSpecification> resources = new HashMap<>();
14301437

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

14331447
/**
14341448
* The Model Context Protocol (MCP) provides a standardized way for servers to
@@ -1684,23 +1698,16 @@ public StatelessAsyncSpecification resources(
16841698
/**
16851699
* Sets the resource templates that define patterns for dynamic resource access.
16861700
* 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>
16951701
* @param resourceTemplates List of resource templates. If null, clears existing
16961702
* templates.
16971703
* @return This builder instance for method chaining
16981704
* @throws IllegalArgumentException if resourceTemplates is null.
16991705
* @see #resourceTemplates(ResourceTemplate...)
17001706
*/
1701-
public StatelessAsyncSpecification resourceTemplates(List<ResourceTemplate> resourceTemplates) {
1707+
public StatelessAsyncSpecification resourceTemplates(
1708+
Map<String, McpStatelessServerFeatures.AsyncResourceTemplateSpecification> resourceTemplatesSpec) {
17021709
Assert.notNull(resourceTemplates, "Resource templates must not be null");
1703-
this.resourceTemplates.addAll(resourceTemplates);
1710+
this.resourceTemplates.putAll(resourceTemplates);
17041711
return this;
17051712
}
17061713

@@ -1712,10 +1719,11 @@ public StatelessAsyncSpecification resourceTemplates(List<ResourceTemplate> reso
17121719
* @throws IllegalArgumentException if resourceTemplates is null.
17131720
* @see #resourceTemplates(List)
17141721
*/
1715-
public StatelessAsyncSpecification resourceTemplates(ResourceTemplate... resourceTemplates) {
1722+
public StatelessAsyncSpecification resourceTemplates(
1723+
McpStatelessServerFeatures.AsyncResourceTemplateSpecification... resourceTemplates) {
17161724
Assert.notNull(resourceTemplates, "Resource templates must not be null");
1717-
for (ResourceTemplate resourceTemplate : resourceTemplates) {
1718-
this.resourceTemplates.add(resourceTemplate);
1725+
for (McpStatelessServerFeatures.AsyncResourceTemplateSpecification resourceTemplate : resourceTemplates) {
1726+
this.resourceTemplates.put(resourceTemplate.resourceTemplate().uriTemplate(), resourceTemplate);
17191727
}
17201728
return this;
17211729
}
@@ -1888,7 +1896,14 @@ class StatelessSyncSpecification {
18881896
*/
18891897
final Map<String, McpStatelessServerFeatures.SyncResourceSpecification> resources = new HashMap<>();
18901898

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

18931908
/**
18941909
* The Model Context Protocol (MCP) provides a standardized way for servers to
@@ -2144,23 +2159,16 @@ public StatelessSyncSpecification resources(
21442159
/**
21452160
* Sets the resource templates that define patterns for dynamic resource access.
21462161
* 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>
21552162
* @param resourceTemplates List of resource templates. If null, clears existing
21562163
* templates.
21572164
* @return This builder instance for method chaining
21582165
* @throws IllegalArgumentException if resourceTemplates is null.
21592166
* @see #resourceTemplates(ResourceTemplate...)
21602167
*/
2161-
public StatelessSyncSpecification resourceTemplates(List<ResourceTemplate> resourceTemplates) {
2168+
public StatelessSyncSpecification resourceTemplates(
2169+
Map<String, McpStatelessServerFeatures.SyncResourceTemplateSpecification> resourceTemplatesSpec) {
21622170
Assert.notNull(resourceTemplates, "Resource templates must not be null");
2163-
this.resourceTemplates.addAll(resourceTemplates);
2171+
this.resourceTemplates.putAll(resourceTemplates);
21642172
return this;
21652173
}
21662174

@@ -2172,10 +2180,11 @@ public StatelessSyncSpecification resourceTemplates(List<ResourceTemplate> resou
21722180
* @throws IllegalArgumentException if resourceTemplates is null.
21732181
* @see #resourceTemplates(List)
21742182
*/
2175-
public StatelessSyncSpecification resourceTemplates(ResourceTemplate... resourceTemplates) {
2183+
public StatelessSyncSpecification resourceTemplates(
2184+
McpStatelessServerFeatures.SyncResourceTemplateSpecification... resourceTemplates) {
21762185
Assert.notNull(resourceTemplates, "Resource templates must not be null");
2177-
for (ResourceTemplate resourceTemplate : resourceTemplates) {
2178-
this.resourceTemplates.add(resourceTemplate);
2186+
for (McpStatelessServerFeatures.SyncResourceTemplateSpecification resourceTemplate : resourceTemplates) {
2187+
this.resourceTemplates.put(resourceTemplate.resourceTemplate().uriTemplate(), resourceTemplate);
21792188
}
21802189
return this;
21812190
}

0 commit comments

Comments
 (0)