The demo module is a complete MCP server, small enough to be read in one sitting, exercising every extension point.

mvn package -pl demo exec:exec

Then register http://localhost:8080/mcp in a client - see Clients .

The tools and the prompt it exposes - names, descriptions, parameters and schemas - are listed in the reference at the end of this page, generated from the module itself.

Source: demo/src/main/java/io/yupiik/fusion/mcp/demo/ - about 200 lines in three classes:

Class Extension point

DemoTools

the @MCPTool methods and the @MCPPrompt one, from the minimal case to sampling, elicitation and roots.

DemoResources

MCPResources: a static resource (demo://greeting) and a template (demo://echo/{message}), neither being a JSON-RPC method so they are not in the generated reference.

DemoCompletions

MCPCompletions: suggesting values for the code argument of demo/prompt.

A guided session

MCP=http://localhost:8080/mcp
SID=$(curl -si -X POST $MCP -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
        "protocolVersion":"2025-06-18","capabilities":{"sampling":{}},
        "clientInfo":{"name":"curl","version":"1"}}}' \
  | grep -i mcp-session-id | tr -d '\r' | cut -d' ' -f2)
post() { curl -s -X POST $MCP -H 'content-type: application/json' -H "mcp-session-id: $SID" -d "$1"; }

post '{"jsonrpc":"2.0","method":"notifications/initialized"}'

# a tool, with its structured result
post '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"demo/greet","arguments":{"name":"fusion","times":3}}}'
# {"result":{"content":[{"text":"{\"something\":\"hello fusion!!!\"}","type":"text"}],
#            "isError":false,"structuredContent":{"something":"hello fusion!!!"}}}

# a failure: readable by the model, not a protocol error
post '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"demo/greet","arguments":{"name":" "}}}'
# {"result":{"content":[{"text":"name should not be blank","type":"text"}],"isError":true, ...}}

# a prompt and the completion of its argument
post '{"jsonrpc":"2.0","id":4,"method":"prompts/get","params":{"name":"demo/prompt","arguments":{"code":"1234"}}}'
post '{"jsonrpc":"2.0","id":5,"method":"completion/complete","params":{
        "ref":{"type":"ref/prompt","name":"demo/prompt"},"argument":{"name":"code","value":"f"}}}'
# {"result":{"completion":{"hasMore":false,"total":1,"values":["fusion"]}}}

# a templated resource
post '{"jsonrpc":"2.0","id":6,"method":"resources/read","params":{"uri":"demo://echo/hello"}}'

Watching the server talk back

Open the stream in one terminal:

curl -N $MCP -H 'accept: text/event-stream' -H "mcp-session-id: $SID"

and call the logging tool in another:

post '{"jsonrpc":"2.0","id":7,"method":"tools/call","params":{"name":"demo/log","arguments":{"message":"hello"}}}'

The stream shows the record:

id: 1
event: message
data: {"jsonrpc":"2.0","method":"notifications/message","params":{"data":"hello","level":"info","logger":"demo"}}

demo/ask goes one step further: it sends a sampling/createMessage request on that stream and waits for the client to post the answer back - the full round trip is scripted in DemoTest#sampling, see Testing .

Native image

mvn package arthur:native-image -pl demo
./demo/target/fusion-mcp-server-demo.graal.bin

No configuration is needed beyond what Fusion generates: no reflection, no dynamic proxy, and the OpenRPC document the tool descriptors come from is registered as a resource by the processor.

Reference

Generated from demo/target/classes/META-INF/fusion/jsonrpc/openrpc.json, i.e. from the signatures themselves - it is the same document the server builds its tool descriptors from.

Methods

demo/ask

Parameter structure: either.

Asks the client model to answer a question.

Parameters:
  • question (string): The question to forward to the client model.

Result: Demo

demo/confirm

Parameter structure: either.

Asks the user to confirm an action.

Parameters:
  • action (string): What has to be confirmed.

Result: Demo

demo/greet

Parameter structure: either.

Greets someone by name.

Parameters:
  • name (string): Who to greet.

  • times (integer): How many times to greet, defaults to 1.

Result: DemoResponse

demo/log

Parameter structure: either.

Sends a log record to the client over the SSE channel.

Parameters:
  • message (string): What to log.

Result: Demo

demo/prompt

Parameter structure: either.

Demo.

Parameters:
  • code (string): The code to inject in the prompt.

Result: PromptResponse

demo/roots

Parameter structure: either.

Lists the workspace roots the client exposes.

This method does not have any parameter.

Result: DemoResponse

demo/search

Parameter structure: either.

Searches the demo catalog.

Parameters:
  • query ( Query ): The query.

  • tags (array with items of type string): Tags to filter on.

  • options (object): Extra options.

  • limit (integer): How many results at most.

  • direction (enum with potential values asc, desc): Sort direction.

Result: DemoResponse

demo/tool

Parameter structure: either.

Demo.

This method does not have any parameter.

Result: Demo

Schemas

Query (io.yupiik.fusion.mcp.demo.DemoTools.Query) schema

Name Type Nullable Description

fuzzy

boolean

false

Should approximate matches be returned too.

text

string

true

The text to search for, mandatory.

Demo (io.yupiik.fusion.mcp.demo.model.Demo) schema

Name Type Nullable Description

greeting

string

true

The greeting sentence, ready to be shown to the user.

DemoResponse (io.yupiik.fusion.mcp.demo.model.DemoResponse) schema

Name Type Nullable Description

something

string

true

What the tool has to say, a plain sentence.

Metadata (io.yupiik.fusion.mcp.model.Metadata) schema

Name Type Nullable Description

name

string

true

Reserved name attribute.

others

object

true

Every other attribute, this object being free form.

title

string

true

Reserved title attribute.

PromptResponse (io.yupiik.fusion.mcp.model.PromptResponse) schema

Name Type Nullable Description

_meta

object of type io.yupiik.fusion.mcp.model.Metadata

true

Optional free form metadata.

description

string

true

What the expanded prompt is about.

messages

array with items of type object of type io.yupiik.fusion.mcp.model.PromptResponse.Message

false

The messages the prompt expands to.

resultType

enum with potential values complete, error, input_required

true

The type of this result, see the {@code 2026-07-28} specification.

Message (io.yupiik.fusion.mcp.model.PromptResponse.Message) schema

Name Type Nullable Description

content

object

true

The message content, a single block or an array of blocks.

role

string

true

Who the message comes from.