Skip to content

Commit 01130c5

Browse files
Kehrlanntzolov
authored andcommitted
Filter special parameters from @McpPrompt schema
- Fixes spring-projects#5750 Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
1 parent 0ff480e commit 01130c5

3 files changed

Lines changed: 116 additions & 9 deletions

File tree

‎mcp/mcp-annotations/src/main/java/org/springframework/ai/mcp/annotation/adapter/PromptAdapter.java‎

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,19 @@
2222
import java.util.List;
2323
import java.util.Map;
2424

25+
import io.modelcontextprotocol.common.McpTransportContext;
26+
import io.modelcontextprotocol.server.McpAsyncServerExchange;
27+
import io.modelcontextprotocol.server.McpSyncServerExchange;
2528
import io.modelcontextprotocol.spec.McpSchema;
2629
import io.modelcontextprotocol.util.Assert;
2730

2831
import org.springframework.ai.mcp.annotation.McpArg;
32+
import org.springframework.ai.mcp.annotation.McpMeta;
33+
import org.springframework.ai.mcp.annotation.McpProgressToken;
2934
import org.springframework.ai.mcp.annotation.McpPrompt;
3035
import org.springframework.ai.mcp.annotation.common.MetaUtils;
36+
import org.springframework.ai.mcp.annotation.context.McpAsyncRequestContext;
37+
import org.springframework.ai.mcp.annotation.context.McpSyncRequestContext;
3138

3239
/**
3340
* Utility class for adapting between McpPrompt annotations and McpSchema.Prompt objects.
@@ -85,8 +92,14 @@ private static List<McpSchema.PromptArgument> extractPromptArguments(Method meth
8592
for (Parameter parameter : parameters) {
8693
// Skip special parameter types
8794
if (McpAsyncServerExchange.class.isAssignableFrom(parameter.getType())
95+
|| McpSyncServerExchange.class.isAssignableFrom(parameter.getType())
96+
|| McpTransportContext.class.isAssignableFrom(parameter.getType())
97+
|| McpSyncRequestContext.class.isAssignableFrom(parameter.getType())
98+
|| McpAsyncRequestContext.class.isAssignableFrom(parameter.getType())
8899
|| McpSchema.GetPromptRequest.class.isAssignableFrom(parameter.getType())
89-
|| java.util.Map.class.isAssignableFrom(parameter.getType())) {
100+
|| java.util.Map.class.isAssignableFrom(parameter.getType())
101+
|| McpMeta.class.isAssignableFrom(parameter.getType())
102+
|| parameter.isAnnotationPresent(McpProgressToken.class)) {
90103
continue;
91104
}
92105

@@ -106,12 +119,4 @@ private static List<McpSchema.PromptArgument> extractPromptArguments(Method meth
106119
return arguments;
107120
}
108121

109-
/**
110-
* Helper interface to avoid direct dependency on McpAsyncServerExchange in this
111-
* package.
112-
*/
113-
private interface McpAsyncServerExchange {
114-
115-
}
116-
117122
}

‎mcp/mcp-annotations/src/test/java/org/springframework/ai/mcp/annotation/provider/prompt/AsyncMcpPromptProviderTests.java‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,4 +562,56 @@ public Mono<List<String>> monoStringListPrompt() {
562562
}).verifyComplete();
563563
}
564564

565+
@Test
566+
void testGetPromptSpecificationsWithSpecialParameters() {
567+
class SpecialParamsPrompt {
568+
569+
@McpPrompt(name = "special-params-prompt", description = "Prompt with special parameters")
570+
public Mono<GetPromptResult> specialParamsPrompt(
571+
@McpArg(name = "name", description = "User's name", required = true) String name,
572+
org.springframework.ai.mcp.annotation.context.McpAsyncRequestContext asyncContext,
573+
GetPromptRequest request,
574+
@org.springframework.ai.mcp.annotation.McpProgressToken String progressToken,
575+
org.springframework.ai.mcp.annotation.McpMeta meta) {
576+
577+
String content = String.format("name=%s,asyncContext=%s,request=%s,progressToken=%s,meta=%s", name,
578+
asyncContext != null ? "bound" : "null", request != null ? "bound" : "null",
579+
progressToken != null ? "bound" : "null", meta != null ? "bound" : "null");
580+
581+
return Mono.just(new GetPromptResult("Special params prompt result",
582+
List.of(new PromptMessage(Role.ASSISTANT, new TextContent(content)))));
583+
}
584+
585+
}
586+
587+
SpecialParamsPrompt promptObject = new SpecialParamsPrompt();
588+
AsyncMcpPromptProvider provider = new AsyncMcpPromptProvider(List.of(promptObject));
589+
590+
List<AsyncPromptSpecification> promptSpecs = provider.getPromptSpecifications();
591+
592+
assertThat(promptSpecs).hasSize(1);
593+
assertThat(promptSpecs.get(0).prompt().name()).isEqualTo("special-params-prompt");
594+
595+
// The schema should only contain the 'name' argument
596+
assertThat(promptSpecs.get(0).prompt().arguments()).hasSize(1);
597+
assertThat(promptSpecs.get(0).prompt().arguments().get(0).name()).isEqualTo("name");
598+
599+
// Test that the handler works with special parameters
600+
McpAsyncServerExchange exchange = mock(McpAsyncServerExchange.class);
601+
Map<String, Object> args = new HashMap<>();
602+
args.put("name", "John");
603+
GetPromptRequest request = new GetPromptRequest("special-params-prompt", args);
604+
Mono<GetPromptResult> result = promptSpecs.get(0).promptHandler().apply(exchange, request);
605+
606+
StepVerifier.create(result).assertNext(promptResult -> {
607+
assertThat(promptResult.description()).isEqualTo("Special params prompt result");
608+
assertThat(promptResult.messages()).hasSize(1);
609+
PromptMessage message = promptResult.messages().get(0);
610+
assertThat(message.role()).isEqualTo(Role.ASSISTANT);
611+
612+
String expectedContent = "name=John,asyncContext=bound,request=bound,progressToken=null,meta=bound";
613+
assertThat(((TextContent) message.content()).text()).isEqualTo(expectedContent);
614+
}).verifyComplete();
615+
}
616+
565617
}

‎mcp/mcp-annotations/src/test/java/org/springframework/ai/mcp/annotation/provider/prompt/SyncMcpPromptProviderTests.java‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,4 +531,54 @@ public List<String> stringListPrompt() {
531531
assertThat(((TextContent) result.messages().get(2).content()).text()).isEqualTo("Third string");
532532
}
533533

534+
@Test
535+
void testGetPromptSpecificationsWithSpecialParameters() {
536+
class SpecialParamsPrompt {
537+
538+
@McpPrompt(name = "special-params-prompt", description = "Prompt with special parameters")
539+
public GetPromptResult specialParamsPrompt(
540+
@McpArg(name = "name", description = "User's name", required = true) String name,
541+
org.springframework.ai.mcp.annotation.context.McpSyncRequestContext syncContext,
542+
GetPromptRequest request,
543+
@org.springframework.ai.mcp.annotation.McpProgressToken String progressToken,
544+
org.springframework.ai.mcp.annotation.McpMeta meta) {
545+
546+
String content = String.format("name=%s,syncContext=%s,request=%s,progressToken=%s,meta=%s", name,
547+
syncContext != null ? "bound" : "null", request != null ? "bound" : "null",
548+
progressToken != null ? "bound" : "null", meta != null ? "bound" : "null");
549+
550+
return new GetPromptResult("Special params prompt result",
551+
List.of(new PromptMessage(Role.ASSISTANT, new TextContent(content))));
552+
}
553+
554+
}
555+
556+
SpecialParamsPrompt promptObject = new SpecialParamsPrompt();
557+
SyncMcpPromptProvider provider = new SyncMcpPromptProvider(List.of(promptObject));
558+
559+
List<SyncPromptSpecification> promptSpecs = provider.getPromptSpecifications();
560+
561+
assertThat(promptSpecs).hasSize(1);
562+
assertThat(promptSpecs.get(0).prompt().name()).isEqualTo("special-params-prompt");
563+
564+
// The schema should only contain the 'name' argument
565+
assertThat(promptSpecs.get(0).prompt().arguments()).hasSize(1);
566+
assertThat(promptSpecs.get(0).prompt().arguments().get(0).name()).isEqualTo("name");
567+
568+
// Test that the handler works with special parameters
569+
McpSyncServerExchange exchange = mock(McpSyncServerExchange.class);
570+
Map<String, Object> args = new HashMap<>();
571+
args.put("name", "John");
572+
GetPromptRequest request = new GetPromptRequest("special-params-prompt", args);
573+
GetPromptResult result = promptSpecs.get(0).promptHandler().apply(exchange, request);
574+
575+
assertThat(result.description()).isEqualTo("Special params prompt result");
576+
assertThat(result.messages()).hasSize(1);
577+
PromptMessage message = result.messages().get(0);
578+
assertThat(message.role()).isEqualTo(Role.ASSISTANT);
579+
580+
String expectedContent = "name=John,syncContext=bound,request=bound,progressToken=null,meta=bound";
581+
assertThat(((TextContent) message.content()).text()).isEqualTo(expectedContent);
582+
}
583+
534584
}

0 commit comments

Comments
 (0)