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:
Executable
+19
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Regenerates every file in output/. Needs JDK 25, Maven, curl, python3.
|
||||
# Session ids and request ids in the wire transcripts differ on every run; everything else is stable
|
||||
# except the one timing line in 07, which is indicative only.
|
||||
set -eu
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# 1-8: the JUnit tests write output/01..08 themselves (the assertions and the transcripts are the same code)
|
||||
mvn -B -q test 2>&1 | tee /tmp/mcp-advanced-test.log | grep -E 'Tests run:|BUILD|ERROR' || true
|
||||
|
||||
# 9: the one WARN the stateless server logs while it silently drops the tools
|
||||
{
|
||||
echo "# log line from the STATELESS test run (spring.ai.mcp.server.protocol=STATELESS)"
|
||||
grep 'Stateless servers' /tmp/mcp-advanced-test.log | sed -E 's/^.*(McpPredicates)/\1/'
|
||||
} > output/09-stateless-warning.txt
|
||||
|
||||
# 10-11: raw JSON-RPC with curl as the client
|
||||
mvn -B -q package -DskipTests
|
||||
bash scripts/wire-demo.sh
|
||||
Executable
+69
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
# Shows the sampling and elicitation messages as raw JSON-RPC over Streamable HTTP, using curl as the
|
||||
# "client". Needs: target/mcp-advanced-1.0.0.jar (mvn package -DskipTests), JDK 25 on PATH, curl, python3.
|
||||
# Output goes to output/10-wire-sampling.txt and output/11-wire-elicitation.txt.
|
||||
set -u
|
||||
cd "$(dirname "$0")/.."
|
||||
URL=http://localhost:8089/mcp
|
||||
H=(-H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream')
|
||||
OUT=output
|
||||
TMP=$(mktemp -d)
|
||||
|
||||
setsid nohup java -Dserver.port=8089 -jar target/mcp-advanced-1.0.0.jar > "$TMP/app.log" 2>&1 < /dev/null &
|
||||
APP_PID=$!
|
||||
for i in $(seq 1 60); do
|
||||
curl -s --noproxy '*' -o /dev/null -X POST "$URL" "${H[@]}" -d '{}' && break
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# --- initialize, declaring BOTH sampling and elicitation ---------------------------------------
|
||||
curl -s --noproxy '*' -D "$TMP/init.h" -o "$TMP/init.b" -X POST "$URL" "${H[@]}" -d '{
|
||||
"jsonrpc":"2.0","id":1,"method":"initialize",
|
||||
"params":{"protocolVersion":"2025-11-25",
|
||||
"capabilities":{"sampling":{},"elicitation":{}},
|
||||
"clientInfo":{"name":"curl-client","version":"0"}}}'
|
||||
SID=$(grep -i '^mcp-session-id:' "$TMP/init.h" | awk '{print $2}' | tr -d '\r')
|
||||
curl -s --noproxy '*' -o /dev/null -X POST "$URL" "${H[@]}" -H "Mcp-Session-Id: $SID" \
|
||||
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
|
||||
|
||||
# Run one tool call, read the server's request out of the SSE stream, answer it, then show the final result.
|
||||
roundtrip () { # $1 = tool name $2 = arguments json $3 = answer result json $4 = output file $5 = title
|
||||
local call_id=7
|
||||
curl -s --noproxy '*' -N -X POST "$URL" "${H[@]}" -H "Mcp-Session-Id: $SID" \
|
||||
-d "{\"jsonrpc\":\"2.0\",\"id\":$call_id,\"method\":\"tools/call\",\"params\":{\"name\":\"$1\",\"arguments\":$2}}" \
|
||||
> "$TMP/stream.txt" &
|
||||
local cpid=$!
|
||||
for i in $(seq 1 40); do grep -q '"method":"\(sampling/createMessage\|elicitation/create\)"' "$TMP/stream.txt" 2>/dev/null && break; sleep 0.25; done
|
||||
local req_line req_id
|
||||
req_line=$(grep '^data:' "$TMP/stream.txt" | grep -E 'sampling/createMessage|elicitation/create' | head -1 | sed 's/^data: *//')
|
||||
req_id=$(python3 -c 'import json,sys; print(json.loads(sys.argv[1])["id"])' "$req_line")
|
||||
curl -s --noproxy '*' -o /dev/null -X POST "$URL" "${H[@]}" -H "Mcp-Session-Id: $SID" \
|
||||
-d "{\"jsonrpc\":\"2.0\",\"id\":\"$req_id\",\"result\":$3}"
|
||||
wait $cpid
|
||||
{
|
||||
echo "# $5"
|
||||
echo
|
||||
echo "## 1. client -> server (POST, answers arrive later on the same session)"
|
||||
echo "tools/call $1 $2"
|
||||
echo
|
||||
echo "## 2. server -> client, on the SSE stream of that same POST"
|
||||
python3 -c 'import json,sys; print(json.dumps(json.loads(sys.argv[1]),indent=2))' "$req_line"
|
||||
echo
|
||||
echo "## 3. client -> server, a NEW POST carrying the answer (same id as the server's request)"
|
||||
python3 -c 'import json,sys; print(json.dumps({"jsonrpc":"2.0","id":sys.argv[1],"result":json.loads(sys.argv[2])},indent=2))' "$req_id" "$3"
|
||||
echo
|
||||
echo "## 4. server -> client, the original tools/call finally completes"
|
||||
grep '^data:' "$TMP/stream.txt" | tail -1 | sed 's/^data: *//' | python3 -c 'import json,sys; print(json.dumps(json.load(sys.stdin),indent=2))'
|
||||
} > "$OUT/$4"
|
||||
}
|
||||
|
||||
roundtrip summarize_incident '{"incidentId":"INC-1043"}' \
|
||||
'{"role":"assistant","content":{"type":"text","text":"A segment merge pushed p99 to 840ms against a 500ms SLO."},"model":"curl-model","stopReason":"endTurn"}' \
|
||||
10-wire-sampling.txt "sampling/createMessage on the wire"
|
||||
|
||||
roundtrip restart_service '{"service":"payments-api"}' \
|
||||
'{"action":"accept","content":{"confirm":true,"ticket":"CHG-2291"}}' \
|
||||
11-wire-elicitation.txt "elicitation/create on the wire"
|
||||
|
||||
kill "$APP_PID" 2>/dev/null
|
||||
rm -rf "$TMP"
|
||||
Reference in New Issue
Block a user