Skip to content

Commit

Permalink
Merge pull request #68 from mkouba/add-logging-capability
Browse files Browse the repository at this point in the history
Add logging capability to the server info
  • Loading branch information
mkouba authored Jan 16, 2025
2 parents 980cebe + c8028db commit 2810fee
Show file tree
Hide file tree
Showing 8 changed files with 30 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -517,11 +517,11 @@ private void processFeatureMethod(AtomicInteger counter, ClassCreator clazz, Met
}
ResultHandle info = metaMethod.newInstance(
MethodDescriptor.ofConstructor(FeatureMethodInfo.class, String.class, String.class, String.class, String.class,
List.class),
List.class, String.class),
metaMethod.load(featureMethod.getName()), metaMethod.load(featureMethod.getDescription()),
featureMethod.getUri() == null ? metaMethod.loadNull() : metaMethod.load(featureMethod.getUri()),
featureMethod.getMimeType() == null ? metaMethod.loadNull() : metaMethod.load(featureMethod.getMimeType()),
args);
args, metaMethod.load(featureMethod.getMethod().declaringClass().name().toString()));
ResultHandle invoker = metaMethod
.newInstance(MethodDescriptor.ofConstructor(featureMethod.getInvoker().getClassName()));
ResultHandle executionModel = metaMethod.load(executionModel(featureMethod.getMethod(), transformedAnnotations));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
* <p>
* See <a href="https://spec.modelcontextprotocol.io/specification/2024-11-05/server/utilities/logging">Logging</a>.
* <p>
* The logger name is derived from the method. For example, if there is a method {@code myTool()} annotated with {@code @Tool}
* The MCP logger name is derived from the method. For example, if there is a method {@code myTool()} annotated with
* {@code @Tool}
* then the logger name will be {@code tool:myTool}.
*/
public interface McpLog {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ protected Object[] prepareArguments(FeatureMetadata<?> metadata, ArgumentProvide
ret[idx] = new RequestId(argProviders.requestId());
} else if (arg.provider() == Provider.MCP_LOG) {
ret[idx] = logs.computeIfAbsent(logKey(metadata),
key -> new McpLogImpl(argProviders.connection()::logLevel, key, argProviders.responder()));
key -> new McpLogImpl(argProviders.connection()::logLevel, metadata.info().declaringClassName(), key,
argProviders.responder()));
} else {
Object val = argProviders.getArg(arg.name());
if (val == null && arg.required()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import java.util.List;

public record FeatureMethodInfo(String name, String description, String uri, String mimeType, List<FeatureArgument> arguments) {
public record FeatureMethodInfo(String name, String description, String uri, String mimeType, List<FeatureArgument> arguments,
String declaringClassName) {

public List<FeatureArgument> serializedArguments() {
if (arguments == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,16 @@

class McpLogImpl implements McpLog {

private final String loggerName;
private final String mcpLoggerName;

private final Supplier<LogLevel> level;

private final Logger log;

private final Responder responder;

McpLogImpl(Supplier<LogLevel> level, String loggerName, Responder responder) {
this.loggerName = loggerName;
McpLogImpl(Supplier<LogLevel> level, String loggerName, String mcpLoggerName, Responder responder) {
this.mcpLoggerName = mcpLoggerName;
this.level = level;
this.log = Logger.getLogger(loggerName);
this.responder = responder;
Expand All @@ -31,15 +31,15 @@ class McpLogImpl implements McpLog {
public void send(LogLevel level, Object data) {
if (isEnabled(Objects.requireNonNull(level))) {
responder.send(Messages.newNotification(McpMessageHandler.NOTIFICATIONS_MESSAGE,
newLog(level, loggerName, encode(data))));
newLog(level, mcpLoggerName, encode(data))));
}
}

@Override
public void send(LogLevel level, String format, Object... params) {
if (isEnabled(Objects.requireNonNull(level))) {
responder.send(Messages.newNotification(McpMessageHandler.NOTIFICATIONS_MESSAGE,
newLog(level, loggerName, format.formatted(params))));
newLog(level, mcpLoggerName, format.formatted(params))));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,10 @@ private Map<String, Object> serverInfo(PromptManager promptManager, ToolManager
if (!toolManager.isEmpty()) {
capabilities.put("tools", Map.of());
}
if (!resourceManager.isEmpty() || resourceTemplateManager.isEmpty()) {
if (!resourceManager.isEmpty() || !resourceTemplateManager.isEmpty()) {
capabilities.put("resources", Map.of());
}
capabilities.put("logging", Map.of());
info.put("capabilities", capabilities);
return info;
}
Expand Down
14 changes: 12 additions & 2 deletions docs/modules/ROOT/pages/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ There are also convenient methods that log the message first (using JBoss Loggin

[source,java]
----
package org.acme;
import io.quarkiverse.mcp.server.Tool;
import io.quarkiverse.mcp.server.ToolArg;
import io.quarkiverse.mcp.server.ToolResponse;
Expand All @@ -303,13 +305,21 @@ public class MyTools {
@Tool
String up(String name, McpLog log) {
log.info("Name accepted %s", name); <1>
log.info("UP name accepted %s", name); <1>
return name.toUpperCase();
}
@Tool
String down(String name, McpLog log) {
log.send(LogLevel.INFO, "DOWN name accepted %s", name); <2>
return name.toLoweCase();
}
}
----
<1> If the tool is called with argument `name=Lu` then (A) an equivalent of `org.jboss.logging.Logger.getLogger("tool:up").infof("Name accepted %s", name)` is called, and (B) subsequently, a notification with parameters `{"level":"info","logger":"tool:up","data":"Name accepted: Lu"}` is sent to the connected client.
<1> If the tool method is called with argument `name=Lu` then (A) an equivalent of `org.jboss.logging.Logger.getLogger("org.acme.MyTools").infof("UP name accepted %s", name)` is called, and (B) subsequently, a notification with parameters `{"level":"info","logger":"tool:up","data":"UP name accepted: Lu"}` is sent to the connected client.
<2> If the tool method is called with argument `name=Foo` then a log message notification with parameters `{"level":"info","logger":"tool:down","data":"DOWN name accepted: Foo"}` is sent to the connected client.


TIP: The default log level can be set with the `quarkus.mcp.server.client-logging.default-level` configuration property.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.net.URISyntaxException;

Expand All @@ -27,6 +28,8 @@ public void testServerInfo() throws URISyntaxException {
assertEquals("quarkus-mcp-server-sse-deployment", serverInfo.getString("name"));
assertEquals(ConfigProvider.getConfig().getOptionalValue("quarkus.application.version", String.class).orElseThrow(),
serverInfo.getString("version"));
JsonObject capabilities = result.getJsonObject("capabilities");
assertTrue(capabilities.containsKey("logging"));
});
}
}

0 comments on commit 2810fee

Please sign in to comment.