Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@
import io.modelcontextprotocol.modern.McpSchema.Prompt;
import io.modelcontextprotocol.modern.McpSchema.PromptArgument;
import io.modelcontextprotocol.modern.McpSchema.PromptMessage;
import io.modelcontextprotocol.modern.McpSchema.ReadResourceOutcome;
import io.modelcontextprotocol.modern.McpSchema.ReadResourceRequest;
import io.modelcontextprotocol.modern.McpSchema.ReadResourceResult;
import io.modelcontextprotocol.modern.McpSchema.Resource;
import io.modelcontextprotocol.modern.McpSchema.Result;
Expand All @@ -50,8 +48,11 @@
import io.modelcontextprotocol.modern.server.feature.CompletionsFeature;
import io.modelcontextprotocol.modern.server.feature.McpChangeBroadcaster;
import io.modelcontextprotocol.modern.server.feature.McpChangeFeed;
import io.modelcontextprotocol.modern.server.feature.McpSyncPrompt;
import io.modelcontextprotocol.modern.server.feature.McpSyncPromptRepository;
import io.modelcontextprotocol.modern.server.feature.McpSyncResource;
import io.modelcontextprotocol.modern.server.feature.McpSyncResourceRepository;
import io.modelcontextprotocol.modern.server.feature.McpSyncTool;
import io.modelcontextprotocol.modern.server.feature.McpSyncToolRepository;
import io.modelcontextprotocol.modern.server.feature.PromptsFeature;
import io.modelcontextprotocol.modern.server.feature.PromptsPage;
Expand Down Expand Up @@ -254,12 +255,9 @@ private static Handler<CallToolRequest, CallToolOutcome> stream(StreamingTool to
return (ctx, request) -> McpSyncResponse.streaming(notifier -> tool.run(ctx, request, notifier));
}

private record ToolEntry(Tool tool, Handler<CallToolRequest, CallToolOutcome> handler) {
}

private static final class ConformanceTools implements McpSyncToolRepository {

private final Map<String, ToolEntry> tools = new LinkedHashMap<>();
private final Map<String, McpSyncTool> tools = new LinkedHashMap<>();

ConformanceTools(McpChangeBroadcaster changes) {
add("test_simple_text", "Returns simple text content for testing",
Expand Down Expand Up @@ -465,27 +463,17 @@ private void add(String name, String description, Handler<CallToolRequest, CallT
}

private void add(Tool tool, Handler<CallToolRequest, CallToolOutcome> handler) {
this.tools.put(tool.name(), new ToolEntry(tool, handler));
this.tools.put(tool.name(), McpSyncTool.of(tool, handler::handle));
}

@Override
public ToolsPage list(McpRequestContext ctx, String cursor) {
return ToolsPage.of(this.tools.values().stream().map(ToolEntry::tool).toList());
}

@Override
public Tool find(McpRequestContext ctx, String name) {
ToolEntry entry = this.tools.get(name);
return entry != null ? entry.tool() : null;
return ToolsPage.of(this.tools.values().stream().map(McpSyncTool::definition).toList());
}

@Override
public McpSyncResponse<CallToolOutcome> call(McpRequestContext ctx, CallToolRequest request) {
ToolEntry entry = this.tools.get(request.name());
if (entry == null) {
throw McpException.invalidParams("Unknown tool: " + request.name());
}
return entry.handler().handle(ctx, request);
public McpSyncTool find(McpRequestContext ctx, String name) {
return this.tools.get(name);
}

private static void sleep(long millis) {
Expand Down Expand Up @@ -530,20 +518,20 @@ public ResourceTemplatesPage listTemplates(McpRequestContext ctx, String cursor)
}

@Override
public McpSyncResponse<ReadResourceOutcome> read(McpRequestContext ctx, ReadResourceRequest request) {
String uri = request.uri();
ReadResourceResult result = switch (uri) {
public McpSyncResource find(McpRequestContext ctx, String uri) {
ReadResourceResult result = read(uri);
return result != null ? McpSyncResource.of((c, request) -> McpSyncResponse.result(result)) : null;
}

private static ReadResourceResult read(String uri) {
return switch (uri) {
case "test://static-text" -> textResource(uri, "This is the content of the static text resource.");
case "test://watched-resource" -> textResource(uri, "This is a watched resource content.");
case "test://static-binary" -> ReadResourceResult
.builder(List.of(BlobResourceContents.builder(uri, RED_PIXEL_PNG).mimeType("image/png").build()))
.build();
default -> templateResource(uri);
};
if (result == null) {
throw McpException.invalidParams("Unknown resource: " + uri, Map.of("uri", uri));
}
return McpSyncResponse.result(result);
}

@Override
Expand Down Expand Up @@ -571,12 +559,9 @@ private static ReadResourceResult templateResource(String uri) {

}

private record PromptEntry(Prompt prompt, Handler<GetPromptRequest, GetPromptOutcome> handler) {
}

private static final class ConformancePrompts implements McpSyncPromptRepository {

private final Map<String, PromptEntry> prompts = new LinkedHashMap<>();
private final Map<String, McpSyncPrompt> prompts = new LinkedHashMap<>();

ConformancePrompts() {
add(Prompt.builder("test_simple_prompt").description("A simple prompt for testing").build(),
Expand Down Expand Up @@ -637,7 +622,7 @@ private static final class ConformancePrompts implements McpSyncPromptRepository
}

private void add(Prompt prompt, Handler<GetPromptRequest, GetPromptOutcome> handler) {
this.prompts.put(prompt.name(), new PromptEntry(prompt, handler));
this.prompts.put(prompt.name(), McpSyncPrompt.of(prompt, handler::handle));
}

private static GetPromptResult messages(PromptMessage... messages) {
Expand All @@ -646,22 +631,12 @@ private static GetPromptResult messages(PromptMessage... messages) {

@Override
public PromptsPage list(McpRequestContext ctx, String cursor) {
return PromptsPage.of(this.prompts.values().stream().map(PromptEntry::prompt).toList());
return PromptsPage.of(this.prompts.values().stream().map(McpSyncPrompt::definition).toList());
}

@Override
public Prompt find(McpRequestContext ctx, String name) {
PromptEntry entry = this.prompts.get(name);
return entry != null ? entry.prompt() : null;
}

@Override
public McpSyncResponse<GetPromptOutcome> get(McpRequestContext ctx, GetPromptRequest request) {
PromptEntry entry = this.prompts.get(request.name());
if (entry == null) {
throw McpException.invalidParams("Unknown prompt: " + request.name());
}
return entry.handler().handle(ctx, request);
public McpSyncPrompt find(McpRequestContext ctx, String name) {
return this.prompts.get(name);
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright 2026-2026 the original author or authors.
*/

package io.modelcontextprotocol.modern.server.feature;

import io.modelcontextprotocol.modern.McpSchema.GetPromptOutcome;
import io.modelcontextprotocol.modern.McpSchema.GetPromptRequest;
import io.modelcontextprotocol.modern.McpSchema.Prompt;
import io.modelcontextprotocol.modern.server.McpAsyncResponse;
import io.modelcontextprotocol.modern.server.McpRequestContext;
import io.modelcontextprotocol.util.Assert;
import reactor.core.publisher.Mono;

/**
* A prompt's definition together with the handler that renders it.
*
* @author Dariusz Jędrzejczyk
*/
public final class McpAsyncPrompt {

private final Prompt definition;

private final Handler handler;

private McpAsyncPrompt(Prompt definition, Handler handler) {
Assert.notNull(definition, "definition must not be null");
Assert.notNull(handler, "handler must not be null");
this.definition = definition;
this.handler = handler;
}

public static McpAsyncPrompt of(Prompt definition, Handler handler) {
return new McpAsyncPrompt(definition, handler);
}

public Prompt definition() {
return this.definition;
}

public Handler handler() {
return this.handler;
}

/** Answers a {@code prompts/get} for this prompt. */
@FunctionalInterface
public interface Handler {

/** The arguments include every required argument of the definition. */
Mono<McpAsyncResponse<GetPromptOutcome>> get(McpRequestContext ctx, GetPromptRequest request);

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@
package io.modelcontextprotocol.modern.server.feature;

import io.modelcontextprotocol.modern.McpException;
import io.modelcontextprotocol.modern.McpSchema.GetPromptOutcome;
import io.modelcontextprotocol.modern.McpSchema.GetPromptRequest;
import io.modelcontextprotocol.modern.McpSchema.Prompt;
import io.modelcontextprotocol.modern.server.McpRequestContext;
import io.modelcontextprotocol.modern.server.McpAsyncResponse;
import reactor.core.publisher.Mono;

/**
Expand All @@ -26,13 +22,6 @@ public interface McpAsyncPromptRepository {
Mono<PromptsPage> list(McpRequestContext ctx, String cursor);

/** The prompt with this name, or empty if there is none. */
Mono<Prompt> find(McpRequestContext ctx, String name);

/**
* Answer a {@code prompts/get}. An unknown prompt is an
* {@link McpException#invalidParams(String) invalid-params error}. The arguments
* include every required argument of the prompt {@link #find} returned.
*/
Mono<McpAsyncResponse<GetPromptOutcome>> get(McpRequestContext ctx, GetPromptRequest request);
Mono<McpAsyncPrompt> find(McpRequestContext ctx, String name);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2026-2026 the original author or authors.
*/

package io.modelcontextprotocol.modern.server.feature;

import io.modelcontextprotocol.modern.McpSchema.ReadResourceOutcome;
import io.modelcontextprotocol.modern.McpSchema.ReadResourceRequest;
import io.modelcontextprotocol.modern.server.McpAsyncResponse;
import io.modelcontextprotocol.modern.server.McpRequestContext;
import io.modelcontextprotocol.util.Assert;
import reactor.core.publisher.Mono;

/**
* A readable resource: the handler that answers {@code resources/read} for it.
*
* @author Dariusz Jędrzejczyk
*/
public final class McpAsyncResource {

private final Handler handler;

private McpAsyncResource(Handler handler) {
Assert.notNull(handler, "handler must not be null");
this.handler = handler;
}

public static McpAsyncResource of(Handler handler) {
return new McpAsyncResource(handler);
}

public Handler handler() {
return this.handler;
}

/** Answers a {@code resources/read} for this resource. */
@FunctionalInterface
public interface Handler {

Mono<McpAsyncResponse<ReadResourceOutcome>> read(McpRequestContext ctx, ReadResourceRequest request);

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@
import java.util.List;

import io.modelcontextprotocol.modern.McpException;
import io.modelcontextprotocol.modern.McpSchema.ReadResourceOutcome;
import io.modelcontextprotocol.modern.McpSchema.ReadResourceRequest;
import io.modelcontextprotocol.modern.server.McpRequestContext;
import io.modelcontextprotocol.modern.server.McpAsyncResponse;
import reactor.core.publisher.Mono;

/**
Expand All @@ -32,11 +29,10 @@ default Mono<ResourceTemplatesPage> listTemplates(McpRequestContext ctx, String
}

/**
* Answer a {@code resources/read}. An unknown resource is an
* {@link McpException#invalidParams(String, Object) invalid-params error} carrying
* {@code {"uri": ...}} as data.
* The resource at this URI, whether listed or matching a template, or empty if there
* is none.
*/
Mono<McpAsyncResponse<ReadResourceOutcome>> read(McpRequestContext ctx, ReadResourceRequest request);
Mono<McpAsyncResource> find(McpRequestContext ctx, String uri);

/**
* Whether {@code resources/updated} is reported for individual resources, advertised
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2026-2026 the original author or authors.
*/

package io.modelcontextprotocol.modern.server.feature;

import io.modelcontextprotocol.modern.McpSchema.CallToolOutcome;
import io.modelcontextprotocol.modern.McpSchema.CallToolRequest;
import io.modelcontextprotocol.modern.McpSchema.Tool;
import io.modelcontextprotocol.modern.server.McpAsyncResponse;
import io.modelcontextprotocol.modern.server.McpRequestContext;
import io.modelcontextprotocol.util.Assert;
import reactor.core.publisher.Mono;

/**
* A tool's definition together with the handler that answers its calls.
*
* @author Dariusz Jędrzejczyk
*/
public final class McpAsyncTool {

private final Tool definition;

private final Handler handler;

private McpAsyncTool(Tool definition, Handler handler) {
Assert.notNull(definition, "definition must not be null");
Assert.notNull(handler, "handler must not be null");
this.definition = definition;
this.handler = handler;
}

public static McpAsyncTool of(Tool definition, Handler handler) {
return new McpAsyncTool(definition, handler);
}

public Tool definition() {
return this.definition;
}

public Handler handler() {
return this.handler;
}

/** Answers a {@code tools/call} for this tool. */
@FunctionalInterface
public interface Handler {

/**
* The arguments conform to the definition's {@code inputSchema}, and a result's
* {@code structuredContent} must conform to its {@code outputSchema}.
*/
Mono<McpAsyncResponse<CallToolOutcome>> call(McpRequestContext ctx, CallToolRequest request);

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@
package io.modelcontextprotocol.modern.server.feature;

import io.modelcontextprotocol.modern.McpException;
import io.modelcontextprotocol.modern.McpSchema.CallToolOutcome;
import io.modelcontextprotocol.modern.McpSchema.CallToolRequest;
import io.modelcontextprotocol.modern.McpSchema.Tool;
import io.modelcontextprotocol.modern.server.McpRequestContext;
import io.modelcontextprotocol.modern.server.McpAsyncResponse;
import reactor.core.publisher.Mono;

/**
Expand All @@ -27,14 +23,6 @@ public interface McpAsyncToolRepository {
Mono<ToolsPage> list(McpRequestContext ctx, String cursor);

/** The tool with this name, or empty if there is none. */
Mono<Tool> find(McpRequestContext ctx, String name);

/**
* Answer a {@code tools/call}. An unknown tool is an
* {@link McpException#invalidParams(String) invalid-params error}. The arguments
* conform to the {@code inputSchema} of the tool {@link #find} returned, and a
* result's {@code structuredContent} must conform to its {@code outputSchema}.
*/
Mono<McpAsyncResponse<CallToolOutcome>> call(McpRequestContext ctx, CallToolRequest request);
Mono<McpAsyncTool> find(McpRequestContext ctx, String name);

}
Loading
Loading