Skip to content

Commit 62cebb6

Browse files
committed
Use InMemory* repositories in tests too
1 parent 224c5b2 commit 62cebb6

9 files changed

Lines changed: 114 additions & 320 deletions

‎mcp-test/src/test/java/io/modelcontextprotocol/modern/server/McpServerMrtrTests.java‎

Lines changed: 28 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,16 @@
2323
import io.modelcontextprotocol.modern.McpSchema.InputRequiredResult;
2424
import io.modelcontextprotocol.modern.McpSchema.MetaKeys;
2525
import io.modelcontextprotocol.modern.McpSchema.ReadResourceResult;
26+
import io.modelcontextprotocol.modern.McpSchema.Resource;
2627
import io.modelcontextprotocol.modern.McpSchema.Result;
2728
import io.modelcontextprotocol.modern.McpSchema.TextContent;
2829
import io.modelcontextprotocol.modern.McpSchema.TextResourceContents;
2930
import io.modelcontextprotocol.modern.McpSchema;
3031
import io.modelcontextprotocol.modern.server.feature.McpAsyncResource;
3132
import io.modelcontextprotocol.modern.server.feature.McpAsyncResourceRepository;
3233
import io.modelcontextprotocol.modern.server.feature.ResourcesFeature;
33-
import io.modelcontextprotocol.modern.server.feature.ResourcesPage;
3434
import io.modelcontextprotocol.modern.server.feature.ToolsFeature;
35+
import io.modelcontextprotocol.modern.server.feature.repository.InMemoryAsyncResourceRepository;
3536
import org.junit.jupiter.api.Test;
3637
import reactor.core.publisher.Mono;
3738

@@ -216,22 +217,11 @@ void inputRequestsForDeclaredCapabilitiesAreSent() {
216217

217218
@Test
218219
void resourceInputRequestsAreCheckedToo() {
219-
McpAsyncResourceRepository repo = new McpAsyncResourceRepository() {
220-
@Override
221-
public Mono<ResourcesPage> list(McpRequestContext ctx, String cursor) {
222-
return Mono.just(ResourcesPage.of(List.of()));
223-
}
224-
225-
@Override
226-
public Mono<McpAsyncResource> find(McpRequestContext ctx, String uri) {
227-
return Mono.just(McpAsyncResource.of(this::read));
228-
}
229-
230-
private Mono<McpAsyncResponse<McpSchema.ReadResourceOutcome>> read(McpRequestContext ctx,
231-
McpSchema.ReadResourceRequest request) {
232-
return Mono.just(McpAsyncResponse.result(InputRequiredResult.builder().listRoots("q1").build()));
233-
}
234-
};
220+
McpAsyncResourceRepository repo = InMemoryAsyncResourceRepository.builder()
221+
.resource(Resource.builder("file:///a.txt", "a").build(),
222+
McpAsyncResource.of((ctx, request) -> Mono
223+
.just(McpAsyncResponse.result(InputRequiredResult.builder().listRoots("q1").build()))))
224+
.build();
235225
McpServer server = baseBuilder()
236226
.feature(ResourcesFeature.ofAsync(repo, McpJsonDefaults.getMapper(), 0L, CacheScope.PRIVATE))
237227
.build();
@@ -245,26 +235,14 @@ private Mono<McpAsyncResponse<McpSchema.ReadResourceOutcome>> read(McpRequestCon
245235

246236
@Test
247237
void retryResultIsMarkedUncacheable() {
248-
McpAsyncResourceRepository repo = new McpAsyncResourceRepository() {
249-
@Override
250-
public Mono<ResourcesPage> list(McpRequestContext ctx, String cursor) {
251-
return Mono.just(ResourcesPage.of(List.of()));
252-
}
253-
254-
@Override
255-
public Mono<McpAsyncResource> find(McpRequestContext ctx, String uri) {
256-
return Mono.just(McpAsyncResource.of(this::read));
257-
}
258-
259-
private Mono<McpAsyncResponse<McpSchema.ReadResourceOutcome>> read(McpRequestContext ctx,
260-
McpSchema.ReadResourceRequest request) {
261-
return Mono.just(McpAsyncResponse.result(ReadResourceResult
262-
.builder(List.of(new TextResourceContents(request.uri(), "text/plain", "content", null)))
263-
.ttlMs(60_000L)
264-
.cacheScope(CacheScope.PUBLIC)
265-
.build()));
266-
}
267-
};
238+
McpAsyncResourceRepository repo = InMemoryAsyncResourceRepository.builder()
239+
.resource(Resource.builder("file:///a.txt", "a").build(),
240+
McpAsyncResource.of((ctx, request) -> Mono.just(McpAsyncResponse.result(ReadResourceResult
241+
.builder(List.of(new TextResourceContents(request.uri(), "text/plain", "content", null)))
242+
.ttlMs(60_000L)
243+
.cacheScope(CacheScope.PUBLIC)
244+
.build()))))
245+
.build();
268246
McpServer server = baseBuilder()
269247
.feature(ResourcesFeature.ofAsync(repo, McpJsonDefaults.getMapper(), 0L, CacheScope.PRIVATE))
270248
.build();
@@ -285,27 +263,20 @@ private Mono<McpAsyncResponse<McpSchema.ReadResourceOutcome>> read(McpRequestCon
285263
@SuppressWarnings("unchecked")
286264
void resourceRequestStateIsBoundToTheUriNotAStrayName() {
287265
AtomicReference<String> seenRequestState = new AtomicReference<>();
288-
McpAsyncResourceRepository repo = new McpAsyncResourceRepository() {
289-
@Override
290-
public Mono<ResourcesPage> list(McpRequestContext ctx, String cursor) {
291-
return Mono.just(ResourcesPage.of(List.of()));
292-
}
293-
294-
@Override
295-
public Mono<McpAsyncResource> find(McpRequestContext ctx, String uri) {
296-
return Mono.just(McpAsyncResource.of(this::read));
297-
}
298-
299-
private Mono<McpAsyncResponse<McpSchema.ReadResourceOutcome>> read(McpRequestContext ctx,
300-
McpSchema.ReadResourceRequest request) {
301-
if (request.requestState() != null) {
302-
seenRequestState.set(request.requestState());
303-
return Mono.just(McpAsyncResponse.result(ReadResourceResult.builder(List.of()).build()));
304-
}
305-
return Mono.just(McpAsyncResponse
306-
.result(InputRequiredResult.builder().elicit("q1", CONFIRM).requestState("for-a").build()));
266+
McpAsyncResource reader = McpAsyncResource.of((ctx, request) -> {
267+
if (request.requestState() != null) {
268+
seenRequestState.set(request.requestState());
269+
return Mono.just(McpAsyncResponse.result(ReadResourceResult.builder(List.of()).build()));
307270
}
308-
};
271+
return Mono.just(McpAsyncResponse
272+
.result(InputRequiredResult.builder().elicit("q1", CONFIRM).requestState("for-a").build()));
273+
});
274+
// Both URIs exist, so the replay against b.txt is rejected for its requestState
275+
// being sealed for a.txt, not for naming an unknown resource.
276+
McpAsyncResourceRepository repo = InMemoryAsyncResourceRepository.builder()
277+
.resource(Resource.builder("file:///a.txt", "a").build(), reader)
278+
.resource(Resource.builder("file:///b.txt", "b").build(), reader)
279+
.build();
309280
McpServer server = baseBuilder()
310281
.feature(ResourcesFeature.ofAsync(repo, McpJsonDefaults.getMapper(), 0L, CacheScope.PRIVATE))
311282
.build();

‎mcp-test/src/test/java/io/modelcontextprotocol/modern/server/ModernTestFixtures.java‎

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import io.modelcontextprotocol.modern.server.feature.McpAsyncToolRepository;
2525
import io.modelcontextprotocol.modern.server.feature.ToolsPage;
2626
import io.modelcontextprotocol.modern.server.feature.McpAsyncTool;
27+
import io.modelcontextprotocol.modern.server.feature.repository.InMemoryAsyncToolRepository;
2728
import io.modelcontextprotocol.util.ToolsUtils;
2829
import reactor.core.publisher.Mono;
2930

@@ -110,17 +111,7 @@ public Mono<McpAsyncTool> find(McpRequestContext ctx, String name) {
110111

111112
/** A tool repository with no tools. */
112113
public static McpAsyncToolRepository emptyTools() {
113-
return new McpAsyncToolRepository() {
114-
@Override
115-
public Mono<ToolsPage> list(McpRequestContext ctx, String cursor) {
116-
return Mono.just(ToolsPage.of(List.of()));
117-
}
118-
119-
@Override
120-
public Mono<McpAsyncTool> find(McpRequestContext ctx, String name) {
121-
return Mono.empty();
122-
}
123-
};
114+
return InMemoryAsyncToolRepository.builder().build();
124115
}
125116

126117
}

‎mcp-test/src/test/java/io/modelcontextprotocol/modern/server/SubscriptionsFeatureTests.java‎

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,14 @@
1818
import io.modelcontextprotocol.modern.McpSchema.SubscriptionFilter;
1919
import io.modelcontextprotocol.modern.McpSchema.SubscriptionsAcknowledgedParams;
2020
import io.modelcontextprotocol.modern.McpSchema;
21-
import io.modelcontextprotocol.modern.server.feature.McpAsyncResource;
2221
import io.modelcontextprotocol.modern.server.feature.McpAsyncResourceRepository;
2322
import io.modelcontextprotocol.modern.server.feature.McpChangeFeed;
2423
import io.modelcontextprotocol.modern.server.feature.McpChangePublisher;
2524
import io.modelcontextprotocol.modern.server.feature.ResourcesFeature;
26-
import io.modelcontextprotocol.modern.server.feature.ResourcesPage;
2725
import io.modelcontextprotocol.modern.server.feature.ServerChange;
2826
import io.modelcontextprotocol.modern.server.feature.ToolsFeature;
27+
import io.modelcontextprotocol.modern.server.feature.repository.InMemoryAsyncResourceRepository;
2928
import org.junit.jupiter.api.Test;
30-
import reactor.core.publisher.Mono;
3129
import reactor.test.StepVerifier;
3230

3331
import static io.modelcontextprotocol.modern.server.ModernTestFixtures.PERMISSIVE_VALIDATOR;
@@ -205,22 +203,9 @@ void resourceSubscribeIsNotAdvertisedWithoutSubscriptions() {
205203
}
206204

207205
private static McpServer resourcesServer(boolean supportsSubscribe, McpChangePublisher publisher) {
208-
McpAsyncResourceRepository repository = new McpAsyncResourceRepository() {
209-
@Override
210-
public Mono<ResourcesPage> list(McpRequestContext ctx, String cursor) {
211-
return Mono.just(ResourcesPage.of(List.of()));
212-
}
213-
214-
@Override
215-
public Mono<McpAsyncResource> find(McpRequestContext ctx, String uri) {
216-
return Mono.empty();
217-
}
218-
219-
@Override
220-
public boolean supportsSubscribe() {
221-
return supportsSubscribe;
222-
}
223-
};
206+
McpAsyncResourceRepository repository = InMemoryAsyncResourceRepository.builder()
207+
.supportsSubscribe(supportsSubscribe)
208+
.build();
224209
McpServer.Builder builder = McpServer.builder()
225210
.serverInfo(SERVER_INFO)
226211
.feature(ResourcesFeature.ofAsync(repository, McpJsonDefaults.getMapper(), 0L, CacheScope.PRIVATE));

‎mcp-test/src/test/java/io/modelcontextprotocol/modern/server/feature/CompletionsFeatureTests.java‎

Lines changed: 27 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import io.modelcontextprotocol.modern.server.McpRequestContext;
2828
import io.modelcontextprotocol.modern.server.McpServer;
2929
import io.modelcontextprotocol.modern.server.McpSyncResponse;
30+
import io.modelcontextprotocol.modern.server.feature.repository.InMemorySyncPromptRepository;
31+
import io.modelcontextprotocol.modern.server.feature.repository.InMemorySyncResourceRepository;
3032
import org.junit.jupiter.api.Test;
3133
import reactor.core.publisher.Mono;
3234

@@ -69,56 +71,24 @@ class CompletionsFeatureTests {
6971
"shop".equals(context.get("database")) ? List.of("orders", "customers") : List.of()));
7072
};
7173

72-
private final McpSyncPromptRepository prompts = new McpSyncPromptRepository() {
73-
@Override
74-
public PromptsPage list(McpRequestContext ctx, String cursor) {
75-
return PromptsPage.of(List.of(CODE_REVIEW, PLAIN));
76-
}
77-
78-
@Override
79-
public McpSyncPrompt find(McpRequestContext ctx, String name) {
80-
return switch (name) {
81-
case "code_review" -> prompt(CODE_REVIEW).withCompletionHandler(languages);
82-
case "plain" -> prompt(PLAIN);
83-
default -> null;
84-
};
85-
}
86-
87-
@Override
88-
public boolean supportsCompletion() {
89-
return true;
90-
}
91-
};
74+
// Completion never reads the templates, so their reader is never called.
75+
private static final McpSyncResource NOT_READ = McpSyncResource.of((ctx, request) -> {
76+
throw new AssertionError("completion must not read the resource");
77+
});
9278

93-
private final McpSyncResourceRepository resources = new McpSyncResourceRepository() {
94-
@Override
95-
public ResourcesPage list(McpRequestContext ctx, String cursor) {
96-
return ResourcesPage.of(List.of());
97-
}
98-
99-
@Override
100-
public ResourceTemplatesPage listTemplates(McpRequestContext ctx, String cursor) {
101-
return ResourceTemplatesPage.of(List.of(TABLE, FILE));
102-
}
103-
104-
@Override
105-
public McpSyncResource find(McpRequestContext ctx, String uri) {
106-
return null;
107-
}
108-
109-
@Override
110-
public McpSyncResourceTemplate findTemplate(McpRequestContext ctx, String uriTemplate) {
111-
if (TABLE.uriTemplate().equals(uriTemplate)) {
112-
return McpSyncResourceTemplate.of(TABLE).withCompletionHandler(tables);
113-
}
114-
return FILE.uriTemplate().equals(uriTemplate) ? McpSyncResourceTemplate.of(FILE) : null;
115-
}
79+
private final McpSyncResourceRepository resources = InMemorySyncResourceRepository.builder()
80+
.template(McpSyncResourceTemplate.of(TABLE).withCompletionHandler(this.tables), NOT_READ)
81+
.template(McpSyncResourceTemplate.of(FILE), NOT_READ)
82+
.supportsCompletion(true)
83+
.build();
11684

117-
@Override
118-
public boolean supportsCompletion() {
119-
return true;
120-
}
121-
};
85+
private McpSyncPromptRepository prompts(boolean supportsCompletion) {
86+
return InMemorySyncPromptRepository.builder()
87+
.prompt(prompt(CODE_REVIEW).withCompletionHandler(this.languages))
88+
.prompt(prompt(PLAIN))
89+
.supportsCompletion(supportsCompletion)
90+
.build();
91+
}
12292

12393
private static McpSyncPrompt prompt(Prompt definition) {
12494
return McpSyncPrompt.of(definition,
@@ -130,7 +100,7 @@ private static McpServer server(McpFeature... features) {
130100
}
131101

132102
private McpServer server() {
133-
return server(PromptsFeature.ofSync(this.prompts), ResourcesFeature.ofSync(this.resources));
103+
return server(PromptsFeature.ofSync(prompts(true)), ResourcesFeature.ofSync(this.resources));
134104
}
135105

136106
private static JSONRPCResponse complete(McpServer server, Map<String, Object> ref, String argument, String value,
@@ -227,7 +197,7 @@ void variableTheTemplateDoesNotDeclareIsInvalidParams() {
227197

228198
@Test
229199
void refTypeNoFeatureCompletesIsInvalidParams() {
230-
McpServer server = server(PromptsFeature.ofSync(this.prompts));
200+
McpServer server = server(PromptsFeature.ofSync(prompts(true)));
231201

232202
JSONRPCResponse response = complete(server, resource(TABLE.uriTemplate()), "table", "", null);
233203

@@ -237,19 +207,7 @@ void refTypeNoFeatureCompletesIsInvalidParams() {
237207

238208
@Test
239209
void completionIsNotServedWhenNoRepositorySupportsIt() {
240-
McpSyncPromptRepository withoutCompletion = new McpSyncPromptRepository() {
241-
@Override
242-
public PromptsPage list(McpRequestContext ctx, String cursor) {
243-
return prompts.list(ctx, cursor);
244-
}
245-
246-
@Override
247-
public McpSyncPrompt find(McpRequestContext ctx, String name) {
248-
return prompts.find(ctx, name);
249-
}
250-
};
251-
252-
JSONRPCResponse response = complete(server(PromptsFeature.ofSync(withoutCompletion)), prompt("code_review"),
210+
JSONRPCResponse response = complete(server(PromptsFeature.ofSync(prompts(false))), prompt("code_review"),
253211
"language", "ja", null);
254212

255213
assertThat(response.error().code()).isEqualTo(ErrorCodes.METHOD_NOT_FOUND);
@@ -274,7 +232,8 @@ public Set<String> completionRefTypes() {
274232
}
275233
};
276234

277-
assertThatIllegalStateException().isThrownBy(() -> server(PromptsFeature.ofSync(this.prompts), promptCompleter))
235+
assertThatIllegalStateException()
236+
.isThrownBy(() -> server(PromptsFeature.ofSync(prompts(true)), promptCompleter))
278237
.withMessageContaining("ref/prompt");
279238
}
280239

@@ -295,23 +254,10 @@ void missingArgumentIsRejectedAsInvalidParams() {
295254
}
296255

297256
private static McpServer serverCompleting(CompleteResult.Completion completion) {
298-
McpSyncPromptRepository prompts = new McpSyncPromptRepository() {
299-
@Override
300-
public PromptsPage list(McpRequestContext ctx, String cursor) {
301-
return PromptsPage.of(List.of(PLAIN));
302-
}
303-
304-
@Override
305-
public McpSyncPrompt find(McpRequestContext ctx, String name) {
306-
return prompt(PLAIN).withCompletionHandler((c, request) -> CompleteResult.of(completion));
307-
}
308-
309-
@Override
310-
public boolean supportsCompletion() {
311-
return true;
312-
}
313-
};
314-
return server(PromptsFeature.ofSync(prompts));
257+
return server(PromptsFeature.ofSync(InMemorySyncPromptRepository.builder()
258+
.prompt(prompt(PLAIN).withCompletionHandler((c, request) -> CompleteResult.of(completion)))
259+
.supportsCompletion(true)
260+
.build()));
315261
}
316262

317263
private static List<String> values(int count) {

0 commit comments

Comments
 (0)