-
Notifications
You must be signed in to change notification settings - Fork 104
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1286 from kdubois/main
MCP Client - Server sample using SSE transport protocol
- Loading branch information
Showing
16 changed files
with
802 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# MCP-based client-server example using the SSE transport protocol | ||
|
||
This sample showcases how to use a Quarkus MCP server to | ||
provide tools to an LLM. In this case, we use the [SSE transport protocol](https://modelcontextprotocol.io/docs/concepts/transports#server-sent-events-sse) giving the LLM a set of tools to interact with for | ||
weather forecast services. | ||
|
||
# Running the sample | ||
|
||
Run the sample by starting the mcp server component in the `mcp-server` directory using `mvn quarkus:dev`. | ||
This will start the server on port 8081. | ||
|
||
Then start the client component in the `mcp-client` directory using `mvn quarkus:dev`. | ||
|
||
# Testing the service | ||
|
||
Go to `http://localhost:8080` and interact with the chatbot (click the icon in the bottom left corner to open the chat | ||
window). | ||
|
||
You can also use the /alerts endpoint to get weather alerts for a specific state without interacting with a chatbot. For example, you can send a GET request to `/alerts?state=New York` to get alerts for the state of New York. |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<modelVersion>4.0.0</modelVersion> | ||
|
||
<groupId>io.quarkiverse.langchain4j</groupId> | ||
<artifactId>quarkus-langchain4j-sample-mcp-client</artifactId> | ||
<name>Quarkus LangChain4j - Sample - Model Context Protocol Client</name> | ||
<version>1.0-SNAPSHOT</version> | ||
|
||
<properties> | ||
<compiler-plugin.version>3.13.0</compiler-plugin.version> | ||
<maven.compiler.release>21</maven.compiler.release> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | ||
|
||
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id> | ||
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id> | ||
<quarkus.platform.version>3.18.3</quarkus.platform.version> | ||
|
||
<quarkus-langchain4j.version>0.24.0</quarkus-langchain4j.version> | ||
|
||
<skipITs>true</skipITs> | ||
<surefire-plugin.version>3.5.0</surefire-plugin.version> | ||
</properties> | ||
|
||
<dependencyManagement> | ||
<dependencies> | ||
<dependency> | ||
<groupId>io.quarkus.platform</groupId> | ||
<artifactId>quarkus-bom</artifactId> | ||
<version>${quarkus.platform.version}</version> | ||
<type>pom</type> | ||
<scope>import</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkiverse.langchain4j</groupId> | ||
<artifactId>quarkus-langchain4j-bom</artifactId> | ||
<version>${quarkus-langchain4j.version}</version> | ||
<type>pom</type> | ||
<scope>import</scope> | ||
</dependency> | ||
</dependencies> | ||
</dependencyManagement> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>io.quarkiverse.langchain4j</groupId> | ||
<artifactId>quarkus-langchain4j-mcp</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkiverse.langchain4j</groupId> | ||
<artifactId>quarkus-langchain4j-openai</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-rest-jackson</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-websockets-next</artifactId> | ||
</dependency> | ||
|
||
<!-- UI --> | ||
<dependency> | ||
<groupId>io.mvnpm</groupId> | ||
<artifactId>importmap</artifactId> | ||
<version>1.0.11</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mvnpm</groupId> | ||
<artifactId>lit</artifactId> | ||
<version>3.2.0</version> | ||
<scope>runtime</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.mvnpm</groupId> | ||
<artifactId>wc-chatbot</artifactId> | ||
<version>0.2.0</version> | ||
<scope>runtime</scope> | ||
</dependency> | ||
|
||
<dependency> | ||
<groupId>io.quarkus</groupId> | ||
<artifactId>quarkus-junit5</artifactId> | ||
<scope>test</scope> | ||
</dependency> | ||
|
||
</dependencies> | ||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>${quarkus.platform.group-id}</groupId> | ||
<artifactId>quarkus-maven-plugin</artifactId> | ||
<version>${quarkus.platform.version}</version> | ||
<extensions>true</extensions> | ||
<executions> | ||
<execution> | ||
<goals> | ||
<goal>build</goal> | ||
<goal>generate-code</goal> | ||
<goal>generate-code-tests</goal> | ||
<goal>native-image-agent</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-compiler-plugin</artifactId> | ||
<version>${compiler-plugin.version}</version> | ||
<configuration> | ||
<parameters>true</parameters> | ||
</configuration> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-surefire-plugin</artifactId> | ||
<version>${surefire-plugin.version}</version> | ||
<configuration> | ||
<systemPropertyVariables> | ||
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager> | ||
<maven.home>${maven.home}</maven.home> | ||
</systemPropertyVariables> | ||
</configuration> | ||
</plugin> | ||
<plugin> | ||
<artifactId>maven-failsafe-plugin</artifactId> | ||
<version>${surefire-plugin.version}</version> | ||
<executions> | ||
<execution> | ||
<goals> | ||
<goal>integration-test</goal> | ||
<goal>verify</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
<configuration> | ||
<systemPropertyVariables> | ||
<native.image.path> | ||
${project.build.directory}/${project.build.finalName}-runner</native.image.path> | ||
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager> | ||
<maven.home>${maven.home}</maven.home> | ||
</systemPropertyVariables> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
|
||
<profiles> | ||
<profile> | ||
<id>native</id> | ||
<activation> | ||
<property> | ||
<name>native</name> | ||
</property> | ||
</activation> | ||
<properties> | ||
<skipITs>false</skipITs> | ||
<quarkus.native.enabled>true</quarkus.native.enabled> | ||
</properties> | ||
</profile> | ||
</profiles> | ||
</project> |
21 changes: 21 additions & 0 deletions
21
.../mcp-client/src/main/java/io/quarkiverse/langchain4j/sample/chatbot/AiWeatherService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package io.quarkiverse.langchain4j.sample.chatbot; | ||
|
||
import dev.langchain4j.service.SystemMessage; | ||
import dev.langchain4j.service.UserMessage; | ||
import io.quarkiverse.langchain4j.RegisterAiService; | ||
|
||
@RegisterAiService( ) | ||
public interface AiWeatherService { | ||
|
||
@SystemMessage("You are a weather expert") | ||
@UserMessage(""" | ||
Get the most recent weather alerts for a given state with state code {state} | ||
""") | ||
String getWeatherAlerts(String state); | ||
|
||
@SystemMessage("You are a weather expert. The user will give you a location, and you should first" + | ||
"get the coordinates for that location, and then based on the coordinates," + | ||
"get the weather for that specific location. " + | ||
"If you can get the US State, then you should also return any weather alerts for the location.") | ||
String getWeather(String message); | ||
} |
28 changes: 28 additions & 0 deletions
28
.../mcp-client/src/main/java/io/quarkiverse/langchain4j/sample/chatbot/ChatBotWebSocket.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package io.quarkiverse.langchain4j.sample.chatbot; | ||
|
||
import io.quarkus.websockets.next.OnOpen; | ||
import io.quarkus.websockets.next.OnTextMessage; | ||
import io.quarkus.websockets.next.WebSocket; | ||
import io.smallrye.common.annotation.Blocking; | ||
|
||
@WebSocket(path = "/chatbot") | ||
public class ChatBotWebSocket { | ||
|
||
private final AiWeatherService aiWeatherService; | ||
|
||
public ChatBotWebSocket(AiWeatherService aiWeatherService) { | ||
this.aiWeatherService = aiWeatherService; | ||
} | ||
|
||
@OnOpen | ||
public String onOpen() { | ||
return "Hello, I am a weather service bot, how can I help?"; | ||
} | ||
|
||
@OnTextMessage | ||
@Blocking | ||
public String onMessage(String message) { | ||
return aiWeatherService.getWeather(message); | ||
} | ||
|
||
} |
51 changes: 51 additions & 0 deletions
51
...mcp-client/src/main/java/io/quarkiverse/langchain4j/sample/chatbot/ImportmapResource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package io.quarkiverse.langchain4j.sample.chatbot; | ||
|
||
import jakarta.annotation.PostConstruct; | ||
import jakarta.enterprise.context.ApplicationScoped; | ||
import jakarta.ws.rs.GET; | ||
import jakarta.ws.rs.Path; | ||
import jakarta.ws.rs.Produces; | ||
|
||
import io.mvnpm.importmap.Aggregator; | ||
|
||
/** | ||
* Dynamically create the import map | ||
*/ | ||
@ApplicationScoped | ||
@Path("/_importmap") | ||
public class ImportmapResource { | ||
private String importmap; | ||
|
||
// See https://github.com/WICG/import-maps/issues/235 | ||
// This does not seem to be supported by browsers yet... | ||
@GET | ||
@Path("/dynamic.importmap") | ||
@Produces("application/importmap+json") | ||
public String importMap() { | ||
return this.importmap; | ||
} | ||
|
||
@GET | ||
@Path("/dynamic-importmap.js") | ||
@Produces("application/javascript") | ||
public String importMapJson() { | ||
return JAVASCRIPT_CODE.formatted(this.importmap); | ||
} | ||
|
||
@PostConstruct | ||
void init() { | ||
Aggregator aggregator = new Aggregator(); | ||
// Add our own mappings | ||
aggregator.addMapping("icons/", "/icons/"); | ||
aggregator.addMapping("components/", "/components/"); | ||
aggregator.addMapping("fonts/", "/fonts/"); | ||
this.importmap = aggregator.aggregateAsJson(); | ||
} | ||
|
||
private static final String JAVASCRIPT_CODE = """ | ||
const im = document.createElement('script'); | ||
im.type = 'importmap'; | ||
im.textContent = JSON.stringify(%s); | ||
document.currentScript.after(im); | ||
"""; | ||
} |
28 changes: 28 additions & 0 deletions
28
...mcp-client/src/main/java/io/quarkiverse/langchain4j/sample/chatbot/MyWeatherResource.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package io.quarkiverse.langchain4j.sample.chatbot; | ||
|
||
import io.quarkus.logging.Log; | ||
import jakarta.inject.Inject; | ||
import jakarta.ws.rs.GET; | ||
import jakarta.ws.rs.Path; | ||
import jakarta.ws.rs.Produces; | ||
import jakarta.ws.rs.QueryParam; | ||
import jakarta.ws.rs.core.MediaType; | ||
|
||
@Path("/alerts") | ||
public class MyWeatherResource { | ||
|
||
@Inject | ||
AiWeatherService aiWeatherService; | ||
|
||
@GET | ||
@Produces(MediaType.TEXT_HTML) | ||
public String getWeatherAlertsForUtah(@QueryParam ("state") String state) { | ||
if (state == null || state.isEmpty()) { | ||
throw new IllegalArgumentException("State parameter is required"); | ||
} | ||
|
||
String weather = aiWeatherService.getWeatherAlerts(state); | ||
Log.info(weather); | ||
return weather; | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
...se-client-server/mcp-client/src/main/resources/META-INF/resources/components/demo-chat.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
import {css, LitElement} from 'lit'; | ||
|
||
export class DemoChat extends LitElement { | ||
|
||
_stripHtml(html) { | ||
const div = document.createElement("div"); | ||
div.innerHTML = html; | ||
return div.textContent || div.innerText || ""; | ||
} | ||
|
||
connectedCallback() { | ||
const chatBot = document.getElementsByTagName("chat-bot")[0]; | ||
|
||
const protocol = (window.location.protocol === 'https:') ? 'wss' : 'ws'; | ||
const socket = new WebSocket(protocol + '://' + window.location.host + '/chatbot'); | ||
|
||
const that = this; | ||
socket.onmessage = function (event) { | ||
chatBot.hideLastLoading(); | ||
// LLM response | ||
let lastMessage; | ||
if (chatBot.messages.length > 0) { | ||
lastMessage = chatBot.messages[chatBot.messages.length - 1]; | ||
} | ||
if (lastMessage && lastMessage.sender.name === "Bot" && ! lastMessage.loading) { | ||
if (! lastMessage.msg) { | ||
lastMessage.msg = ""; | ||
} | ||
lastMessage.msg += event.data; | ||
let bubbles = chatBot.shadowRoot.querySelectorAll("chat-bubble"); | ||
let bubble = bubbles.item(bubbles.length - 1); | ||
if (lastMessage.message) { | ||
bubble.innerHTML = that._stripHtml(lastMessage.message) + lastMessage.msg; | ||
} else { | ||
bubble.innerHTML = lastMessage.msg; | ||
} | ||
chatBot.body.scrollTo({ top: chatBot.body.scrollHeight, behavior: 'smooth' }) | ||
} else { | ||
chatBot.sendMessage(event.data, { | ||
right: false, | ||
sender: { | ||
name: "Bot" | ||
} | ||
}); | ||
} | ||
} | ||
|
||
chatBot.addEventListener("sent", function (e) { | ||
if (e.detail.message.sender.name !== "Bot") { | ||
// User message | ||
const msg = that._stripHtml(e.detail.message.message); | ||
socket.send(msg); | ||
chatBot.sendMessage("", { | ||
right: false, | ||
loading: true | ||
}); | ||
} | ||
}); | ||
} | ||
|
||
|
||
} | ||
|
||
customElements.define('demo-chat', DemoChat); |
Oops, something went wrong.