Skip to content

Commit

Permalink
Merge pull request #109 from mkouba/dev-mode-dummy-init
Browse files Browse the repository at this point in the history
sse: introduce dummy init in the dev mode
  • Loading branch information
mkouba authored Feb 13, 2025
2 parents 33f0a5b + 337bfa2 commit 45cf0ab
Show file tree
Hide file tree
Showing 11 changed files with 92 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io.quarkiverse.mcp.server.McpConnection;
import io.quarkiverse.mcp.server.McpLog.LogLevel;
import io.quarkiverse.mcp.server.runtime.config.McpRuntimeConfig;
import io.quarkus.runtime.LaunchMode;
import io.vertx.core.json.JsonObject;

public class McpMessageHandler {
Expand Down Expand Up @@ -70,6 +71,17 @@ private void initializeNew(JsonObject message, Responder responder, McpConnectio
// The first message must be "initialize"
String method = message.getString("method");
if (!INITIALIZE.equals(method)) {
// In the dev mode, if an MCP client attempts to reconnect an SSE connection but does not reinitialize propertly,
// we could perform a "dummy" initialization
if (LaunchMode.current() == LaunchMode.DEVELOPMENT && config.devMode().dummyInit()) {
InitializeRequest dummy = new InitializeRequest(new Implementation("dummy", "1"), DEFAULT_PROTOCOL_VERSION,
List.of());
if (connection.initialize(dummy) && connection.setInitialized()) {
LOG.infof("Connection initialized with dummy info [%s]", connection.id());
operation(message, responder, connection);
return;
}
}
responder.sendError(id, JsonRPC.METHOD_NOT_FOUND,
"The first message from the client must be \"initialize\": " + method);
return;
Expand Down Expand Up @@ -198,7 +210,7 @@ private void ping(JsonObject message, Responder responder) {

private void close(JsonObject message, Responder responder, McpConnection connection) {
if (connectionManager.remove(connection.id())) {
LOG.debugf("Connection %s closed", connection.id());
LOG.debugf("Connection %s explicitly closed ", connection.id());
} else {
responder.sendError(message.getValue("id"), JsonRPC.INTERNAL_ERROR,
"Unable to obtain the connection to be closed:" + connection.id());
Expand All @@ -220,10 +232,12 @@ private InitializeRequest decodeInitializeRequest(JsonObject params) {
return new InitializeRequest(implementation, protocolVersion, clientCapabilities);
}

private static final String DEFAULT_PROTOCOL_VERSION = "2024-11-05";

private Map<String, Object> serverInfo(PromptManagerImpl promptManager, ToolManagerImpl toolManager,
ResourceManagerImpl resourceManager, ResourceTemplateManagerImpl resourceTemplateManager, McpMetadata metadata) {
Map<String, Object> info = new HashMap<>();
info.put("protocolVersion", "2024-11-05");
info.put("protocolVersion", DEFAULT_PROTOCOL_VERSION);

String serverName = config.serverInfo().name()
.orElse(ConfigProvider.getConfig().getOptionalValue("quarkus.application.name", String.class).orElse("N/A"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.jboss.logging.Logger;

import io.quarkiverse.mcp.server.McpConnection;
import io.vertx.core.json.JsonObject;

public class TrafficLogger {
Expand All @@ -14,12 +15,12 @@ public TrafficLogger(int textPayloadLimit) {
this.textPayloadLimit = textPayloadLimit;
}

public void messageReceived(JsonObject message) {
LOG.infof("JSON message received:\n\n%s", messageToString(message));
public void messageReceived(JsonObject message, McpConnection connection) {
LOG.infof("MCP message received [%s]:\n\n%s", connection.id(), messageToString(message));
}

public void messageSent(JsonObject message) {
LOG.infof("JSON message sent:\n\n%s", messageToString(message));
public void messageSent(JsonObject message, McpConnection connection) {
LOG.infof("MCP message sent [%s]:\n\n%s", connection.id(), messageToString(message));
}

private String messageToString(JsonObject message) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,25 @@ public interface McpRuntimeConfig {
*/
Optional<Duration> autoPingInterval();

/**
* Dev mode config.
*/
DevMode devMode();

public interface TrafficLogging {

/**
* If set to true then JSON messages received/sent are logged.
* If set to `true` then JSON messages received/sent are logged.
*
* @asciidoclet
*/
@WithDefault("false")
public boolean enabled();

/**
* The number of characters of a text message which will be logged if traffic logging is enabled.
*
* @asciidoclet
*/
@WithDefault("200")
public int textLimit();
Expand Down Expand Up @@ -79,10 +88,25 @@ public interface ClientLogging {

/**
* The default log level.
*
* @asciidoclet
*/
@WithDefault("INFO")
public LogLevel defaultLevel();

}

public interface DevMode {

/**
* If set to `true` then if an MCP client attempts to reconnect an SSE connection but does not reinitialize properly,
* the server will perform a "dummy" initialization; capability negotiation and protocol version agreement is skipped.
*
* @asciidoclet
*/
@WithDefault("true")
public boolean dummyInit();

}

}
20 changes: 19 additions & 1 deletion docs/modules/ROOT/pages/includes/quarkus-mcp-server-core.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ a| [[quarkus-mcp-server-core_quarkus-mcp-server-traffic-logging-enabled]] [.prop

[.description]
--
If set to true then JSON messages received/sent are logged.
If set to `true` then JSON messages received/sent are logged.


ifdef::add-copy-button-to-env-var[]
Expand Down Expand Up @@ -115,6 +115,24 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-quarkus-mcp-server-core_quarkus-mcp[icon:question-circle[title=More information about the Duration format]]
|

a| [[quarkus-mcp-server-core_quarkus-mcp-server-dev-mode-dummy-init]] [.property-path]##link:#quarkus-mcp-server-core_quarkus-mcp-server-dev-mode-dummy-init[`quarkus.mcp.server.dev-mode.dummy-init`]##

[.description]
--
If set to `true` then if an MCP client attempts to reconnect an SSE connection but does not reinitialize propertly,
the server will perform a "dummy" initialization; capability negotiation and protocol version agreement is skipped.


ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++QUARKUS_MCP_SERVER_DEV_MODE_DUMMY_INIT+++[]
endif::add-copy-button-to-env-var[]
ifndef::add-copy-button-to-env-var[]
Environment variable: `+++QUARKUS_MCP_SERVER_DEV_MODE_DUMMY_INIT+++`
endif::add-copy-button-to-env-var[]
--
|boolean
|`true`

|===

ifndef::no-duration-note[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ a| [[quarkus-mcp-server-core_quarkus-mcp-server-traffic-logging-enabled]] [.prop

[.description]
--
If set to true then JSON messages received/sent are logged.
If set to `true` then JSON messages received/sent are logged.


ifdef::add-copy-button-to-env-var[]
Expand Down Expand Up @@ -115,6 +115,24 @@ endif::add-copy-button-to-env-var[]
|link:https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/time/Duration.html[Duration] link:#duration-note-anchor-quarkus-mcp-server-core_quarkus-mcp[icon:question-circle[title=More information about the Duration format]]
|

a| [[quarkus-mcp-server-core_quarkus-mcp-server-dev-mode-dummy-init]] [.property-path]##link:#quarkus-mcp-server-core_quarkus-mcp-server-dev-mode-dummy-init[`quarkus.mcp.server.dev-mode.dummy-init`]##

[.description]
--
If set to `true` then if an MCP client attempts to reconnect an SSE connection but does not reinitialize propertly,
the server will perform a "dummy" initialization; capability negotiation and protocol version agreement is skipped.


ifdef::add-copy-button-to-env-var[]
Environment variable: env_var_with_copy_button:+++QUARKUS_MCP_SERVER_DEV_MODE_DUMMY_INIT+++[]
endif::add-copy-button-to-env-var[]
ifndef::add-copy-button-to-env-var[]
Environment variable: `+++QUARKUS_MCP_SERVER_DEV_MODE_DUMMY_INIT+++`
endif::add-copy-button-to-env-var[]
--
|boolean
|`true`

|===

ifndef::no-duration-note[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ a|icon:lock[title=Fixed at build time] [[quarkus-mcp-server-sse_quarkus-root-pat

[.description]
--
The SSE endpoint is exposed at `{rootPath}/sse`. By default, it's `/mcp/sse`.
The SSE endpoint is exposed at `\{rootPath}/sse`. By default, it's `/mcp/sse`.


ifdef::add-copy-button-to-env-var[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void send(JsonObject message) {
return;
}
if (trafficLogger != null) {
trafficLogger.messageSent(message);
trafficLogger.messageSent(message, this);
}
sendEvent("message", message.encode());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public void handle(RoutingContext ctx) {
return;
}
if (connection.trafficLogger() != null) {
connection.trafficLogger().messageReceived(message);
connection.trafficLogger().messageReceived(message, connection);
}
if (JsonRPC.validate(message, connection)) {
handle(message, connection, connection);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,18 @@ public void handle(RoutingContext ctx) {

String id = Base64.getUrlEncoder().encodeToString(UUID.randomUUID().toString().getBytes());

LOG.debugf("Client connection initialized [%s]", id);
LOG.debugf("Connection initialized [%s]", id);

SseMcpConnection connection = new SseMcpConnection(id, config.clientLogging().defaultLevel(), trafficLogger,
config.autoPingInterval(), response);
connectionManager.add(connection);

// TODO we cannot override the close handler set/used by Quarkus HTTP
setCloseHandler(ctx.request(), id, connectionManager);

// By default /mcp/messages/{generatedId}
String endpointPath = mcpPath.endsWith("/") ? (mcpPath + "messages/" + id) : (mcpPath + "/messages/" + id);
LOG.debugf("POST endpoint path: %s [%s]", endpointPath, id);
LOG.debugf("POST endpoint path: %s", endpointPath);

// https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/#http-with-sse
connection.sendEvent("endpoint", endpointPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void send(JsonObject message) {
return;
}
if (trafficLogger != null) {
trafficLogger.messageSent(message);
trafficLogger.messageSent(message, this);
}
if (BlockingOperationControl.isBlockingAllowed()) {
out.println(message.encode());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public void run() {
return;
}
if (trafficLogger != null) {
trafficLogger.messageReceived(message);
trafficLogger.messageReceived(message, connection);
}
if (JsonRPC.validate(message, connection)) {
handle(message, connection, connection);
Expand Down

0 comments on commit 45cf0ab

Please sign in to comment.