Add mcp-advanced module: resources, prompts, sampling and elicitation on Spring AI 2.0.2 and MCP Java SDK 2.1.0

An incident-desk MCP server with a fixed resource, two resource templates, a prompt, a tool that borrows the client's model (sampling) and a tool that asks the operator to approve a restart (elicitation). Driven by a plain SDK client in JUnit and by raw curl, so the JSON-RPC for both server-to-client requests is committed. Also captured: a client that declared neither capability (a gated tool degrades, an ungated one returns an isError result), a slow client hitting the server request timeout, and protocol=STATELESS silently dropping every tool that takes McpSyncRequestContext (one WARN line, an empty tools/list). Negotiated protocol version is 2025-11-25; the 2026-07-28 spec is not implemented by SDK 2.1.0. No docs/ folder.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017ic4b5jAFLJrmUXmbo5EuV
This commit is contained in:
2026-11-10 18:47:09 +00:00
co-authored by Claude Sonnet 5.5
parent 68da642f9a
commit 0006167c24
27 changed files with 1033 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
# initialize handshake (client declares sampling + elicitation)
server.name: incident-desk
negotiated protocolVersion: 2025-11-25
server capability: tools: true
server capability: resources: true
server capability: prompts: true
server capability: logging: true
client declared: sampling: true
client declared: elicitation: true
+13
View File
@@ -0,0 +1,13 @@
# resources/list, resources/templates/list, resources/read
resource incident://index name=incident-index mime=text/plain
template incident://{id}/log name=incident-log
template runbook://{service} name=runbook
read incident://index ->
INC-1042
INC-1043
read incident://INC-1043/log ->
11:02:44 search-api WARN index segment merge running
11:02:51 search-api INFO p99 latency 840ms (SLO 500ms)
read incident://INC-9999/log -> McpError: Error invoking resource method: log in com.ankurm.mcpadvanced.IncidentResources. /nCause: No such incident: INC-9999
+13
View File
@@ -0,0 +1,13 @@
# prompts/list and prompts/get
prompt postmortem -- Draft a blameless postmortem for an incident
arg incidentId required=true
arg audience required=false
get postmortem(INC-1043, on-call manager) ->
[USER] Write a blameless postmortem for the on-call manager. Sections: impact, timeline, root cause, follow-ups. Use only the log below.
11:02:44 search-api WARN index segment merge running
11:02:51 search-api INFO p99 latency 840ms (SLO 500ms)
get postmortem(INC-9999) -> McpError: Error invoking prompt method: postmortem in com.ankurm.mcpadvanced.IncidentPrompts./nCause: No such incident: INC-9999
+13
View File
@@ -0,0 +1,13 @@
# tools/call summarize_incident -> server sends sampling/createMessage to the client
what the CLIENT received from the server:
systemPrompt: You are an SRE. Answer in two sentences, no preamble.
messages: 1
messages[0].role: USER
messages[0] starts with: Summarize this incident log:
maxTokens: 120
temperature: 0.2
includeContext: NONE
tool result returned to the caller:
Connection pool on payments-api was exhausted by a 14.8s ledger query, so 212 clients got HTTP 503. Fix the query or raise the pool.
isError: false
+8
View File
@@ -0,0 +1,8 @@
# tools/call restart_service -> server sends elicitation/create to the client
message shown to the human: Restart payments-api? This drops in-flight requests.
requestedSchema sent by the server: {type=object, properties={confirm={type=boolean, description=Tick to approve the restart}, ticket={type=string, description=Change ticket number, e.g. CHG-2291}}, required=[confirm, ticket]}
ACCEPT confirm=true ticket=CHG-2291: RESTARTED payments-api under CHG-2291
ACCEPT confirm=false: NOT RESTARTED -- form submitted with the box unticked
DECLINE: NOT RESTARTED -- operator declined
CANCEL : NOT RESTARTED -- operator dismissed the dialog
@@ -0,0 +1,14 @@
# a client that declared neither sampling nor elicitation
summarize_incident (gated with sampleEnabled()):
SAMPLING_UNAVAILABLE -- client has no model to lend. Raw log:
11:02:44 search-api WARN index segment merge running
11:02:51 search-api INFO p99 latency 840ms (SLO 500ms)
restart_service (gated with elicitEnabled()):
ELICITATION_UNAVAILABLE -- refusing to restart payments-api without an approval channel
summarize_incident_ungated (calls sample() with no check):
result: Sampling not supported by the client: Implementation[name=Java SDK MCP Client, title=null, version=0.15.0, description=null, icons=null, websiteUrl=null]
Sampling not supported by the client: Implementation[name=Java SDK MCP Client, title=null, version=0.15.0, description=null, icons=null, websiteUrl=null]
isError: true
@@ -0,0 +1,5 @@
# client model takes 5s, server spring.ai.mcp.server.request-timeout=3s
outcome: result isError=true text=java.util.concurrent.TimeoutException: Did not observe any item or terminal signal within 3000ms in 'source(MonoCreate)' (and no fallback has been configured)
Did not observe any item or terminal signal within 3000ms in 'source(MonoCreate)' (and no fallback has been configured)
returned in about (seconds, rounded): 3
(timing is indicative, not a result)
@@ -0,0 +1,5 @@
# spring.ai.mcp.server.protocol=STATELESS, client declares sampling + elicitation
tools listed: []
summarize_incident: exception McpError: Unknown tool: invalid_tool_name
restart_service: exception McpError: Unknown tool: invalid_tool_name
times the client's handlers were called: 0
@@ -0,0 +1,4 @@
# log line from the STATELESS test run (spring.ai.mcp.server.protocol=STATELESS)
McpPredicates : Stateless servers doesn't support bidirectional parameters. Skipping method public java.lang.String com.ankurm.mcpadvanced.IncidentTools.restart(java.lang.String,org.springframework.ai.mcp.annotation.context.McpSyncRequestContext) with bidirectional parameters
McpPredicates : Stateless servers doesn't support bidirectional parameters. Skipping method public java.lang.String com.ankurm.mcpadvanced.IncidentTools.summarize(java.lang.String,org.springframework.ai.mcp.annotation.context.McpSyncRequestContext) with bidirectional parameters
McpPredicates : Stateless servers doesn't support bidirectional parameters. Skipping method public java.lang.String com.ankurm.mcpadvanced.IncidentTools.summarizeUngated(java.lang.String,org.springframework.ai.mcp.annotation.context.McpSyncRequestContext) with bidirectional parameters
+56
View File
@@ -0,0 +1,56 @@
# sampling/createMessage on the wire
## 1. client -> server (POST, answers arrive later on the same session)
tools/call summarize_incident {"incidentId":"INC-1043"}
## 2. server -> client, on the SSE stream of that same POST
{
"jsonrpc": "2.0",
"method": "sampling/createMessage",
"id": "7899c995-7e19-45a9-8b27-a043acabe802-0",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Summarize this incident log:\n11:02:44 search-api WARN index segment merge running\n11:02:51 search-api INFO p99 latency 840ms (SLO 500ms)\n"
}
}
],
"systemPrompt": "You are an SRE. Answer in two sentences, no preamble.",
"includeContext": "none",
"temperature": 0.2,
"maxTokens": 120
}
}
## 3. client -> server, a NEW POST carrying the answer (same id as the server's request)
{
"jsonrpc": "2.0",
"id": "7899c995-7e19-45a9-8b27-a043acabe802-0",
"result": {
"role": "assistant",
"content": {
"type": "text",
"text": "A segment merge pushed p99 to 840ms against a 500ms SLO."
},
"model": "curl-model",
"stopReason": "endTurn"
}
}
## 4. server -> client, the original tools/call finally completes
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"content": [
{
"type": "text",
"text": "A segment merge pushed p99 to 840ms against a 500ms SLO."
}
],
"isError": false
}
}
@@ -0,0 +1,61 @@
# elicitation/create on the wire
## 1. client -> server (POST, answers arrive later on the same session)
tools/call restart_service {"service":"payments-api"}
## 2. server -> client, on the SSE stream of that same POST
{
"jsonrpc": "2.0",
"method": "elicitation/create",
"id": "7899c995-7e19-45a9-8b27-a043acabe802-1",
"params": {
"message": "Restart payments-api? This drops in-flight requests.",
"requestedSchema": {
"type": "object",
"properties": {
"confirm": {
"type": "boolean",
"description": "Tick to approve the restart"
},
"ticket": {
"type": "string",
"description": "Change ticket number, e.g. CHG-2291"
}
},
"required": [
"confirm",
"ticket"
]
},
"_meta": {},
"mode": "form"
}
}
## 3. client -> server, a NEW POST carrying the answer (same id as the server's request)
{
"jsonrpc": "2.0",
"id": "7899c995-7e19-45a9-8b27-a043acabe802-1",
"result": {
"action": "accept",
"content": {
"confirm": true,
"ticket": "CHG-2291"
}
}
}
## 4. server -> client, the original tools/call finally completes
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"content": [
{
"type": "text",
"text": "RESTARTED payments-api under CHG-2291"
}
],
"isError": false
}
}