Connecting a client
This page is about the end user side: making a running server usable from a real MCP client, and understanding what the user and the model actually see.
Start a server
mvn package -pl demo exec:exec # the demo of this repository, on http://localhost:8080/mcp
Any Fusion application embedding fusion-mcp-server-impl behaves the same, see Setup
.
Claude Code
claude mcp add --transport http demo http://localhost:8080/mcp
or declare it in the project configuration:
{
"mcpServers": {
"demo": {
"type": "http",
"url": "http://localhost:8080/mcp"
}
}
}
Then /mcp in the session lists the server, its tools and its prompts. A prompt shows up as a slash command, a resource can be attached with @.
Claude Desktop and other clients
Same declaration, in the client configuration file (claude_desktop_config.json for Claude Desktop):
{"mcpServers": {"demo": {"type": "http", "url": "http://localhost:8080/mcp"}}}
Clients which only support the stdio transport need a bridge - npx mcp-remote http://localhost:8080/mcp - since this server is HTTP only:
{
"mcpServers": {
"demo": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:8080/mcp"]
}
}
}
MCP inspector
The reference debugging UI, it lists everything the server exposes and lets you call it by hand:
npx @modelcontextprotocol/inspector
Pick the Streamable HTTP transport and http://localhost:8080/mcp
. It is the fastest way to check a tool description and schema read well before letting a model use them.
By hand, with curl
Everything is plain JSON-RPC over HTTP, so curl is enough to explore or to script a smoke test:
MCP=http://localhost:8080/mcp
# initialize, keep the session id
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":{},
"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"}'
post '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
post '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"demo/greet","arguments":{"name":"fusion"}}}'
post '{"jsonrpc":"2.0","id":4,"method":"resources/read","params":{"uri":"demo://greeting"}}'
# watch what the server pushes
curl -N $MCP -H 'accept: text/event-stream' -H "mcp-session-id: $SID"
What the user sees
| Exposed as | User experience |
|---|---|
|
Tool |
invisible until the model decides to call it; the client then shows the tool name, the arguments and asks for a confirmation. The |
|
Prompt |
an explicit entry - slash command, menu - with one input per argument, completed as the user types when a |
|
Resource |
something to attach to the conversation ( |
|
Log record |
in the client logs or its MCP panel, filtered by the level the user picked. |
|
Sampling request |
a confirmation dialog: the server is asking to use the user model, and possibly tokens. |
|
Elicitation request |
a form built from the requested schema. |
Writing for a model
The description and the argument documentation are the interface with the model. What works:
, * name the arguments after domain concepts, and document their format: "ISO 4217 currency, EUR by default", * make failures actionable: "unknown product id, list them with catalog/search" beats a stack trace, * keep the tool set small and orthogonal; ten precise tools beat one tool with a mode` argument. Check the result with the inspector or tools/list: what you read there is exactly what the model reads.
Troubleshooting
| Symptom | Cause |
|---|---|
|
|
the session expired ( |
|
|
the client announces a version this server does not support, see Protocol negotiation . |
|
A tool is missing from |
the method is not annotated with |
|
|
the name is not a |
|
The client receives no notification |
it did not open the |
|
|
the client did not declare the capability in |
|
A tool answers |
the tool threw; this is by design so the model can react. The message is in the text content. |
Enable the access log (fusion.http-server.accessLog=true) to see the MCP traffic, and set the client log level to debug through logging/setLevel to get everything the server logs.