Skip to content

Commit df4db7a

Browse files
committed
blog about mcp server
1 parent 6dd0370 commit df4db7a

File tree

5 files changed

+283
-0
lines changed

5 files changed

+283
-0
lines changed

_posts/2024-01-29-ngrok.adoc

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
layout: post
3+
title: 'ngrok: Expose your local services to the Internet'
4+
date: 2024-01-29
5+
tags: development-tips
6+
synopsis: 'ngrok is a tool that allows you to expose your local services and the Quarkus integration have some recent updates.'
7+
author: maxandersen
8+
---
9+
:imagesdir: /assets/images/posts/ngrok
10+
11+
Imagine you are working on a Zulip chatbot or GitHub integration or some other Quarkus application that uses webhooks to call into your application. How do you test that locally?
12+
13+
You need to somehow expose your local service to the internet so that the webhook can be (pun intended) hooked in. There are various options out there like ultrahook, localtunnel, and ngrok.
14+
15+
In this post, we will look at ngrok and how you can use it with Quarkus.
16+
17+
https://ngrok.com[ngrok] is a local running agent that via a cloud service allows you to expose your local services to the internet.
18+
19+
20+

_posts/2025-01-13-mcp-server.adoc

+263
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
= Implementing a MCP server in Quarkus
2+
:page-layout: post
3+
:page-title: 'Implementing a MCP server in Quarkus'
4+
:page-date: 2025-01-13
5+
:page-tags: [langchain4j, llm, ai]
6+
:page-synopsis: Shows how to implement an MCP server in Quarkus and use it in various clients such as Claude Desktop and LangChain4j
7+
:page-author: maxandersen
8+
:imagesdir: /assets/images/posts/mcp
9+
ifdef::env-github,env-browser,env-vscode[:imagesdir: ../assets/images/posts/mcp]
10+
11+
The Model Context Protocol (MCP) is an emerging standard that enables AI models to safely interact with external tools and resources. In this tutorial, I'll show you how to implement an MCP server using Quarkus, allowing you to extend AI applications with custom tools powered by the Java ecosystem.
12+
13+
== What we'll be building
14+
15+
We'll implement a simple MCP server that provides tools to get weather forecasts and alerts for US-based locations. We've chosen this example because it aligns with the official MCP quickstart guide at https://modelcontextprotocol.io/quickstart/server[modelcontextprotocol.io/quickstart/server], making it easier to compare implementations across different languages.
16+
17+
Our server will expose two tools: `getAlerts` and `getForecast`. Once built, we'll connect it to an MCP host that runs the server as a subprocess. Here's how it looks when integrated with Claude:
18+
19+
image::claude-example.png[Claude MCP Integration Example]
20+
21+
== Core MCP Concepts
22+
23+
MCP servers can provide three main types of capabilities:
24+
25+
Resources:: File-like data that can be read by clients (like API responses or file contents)
26+
Tools:: Functions that can be called by the LLM (with user approval)
27+
Prompts:: Pre-written templates that help users accomplish specific tasks
28+
29+
This tutorial focuses on implementing tools.
30+
31+
=== Prerequisites
32+
33+
To follow this tutorial you need:
34+
35+
* Familiarity with Quarkus and Java
36+
* Understanding of LLMs (OpenAI, Granite, Anthropic, Google, etc.)
37+
38+
=== System requirements
39+
40+
* Quarkus CLI
41+
* JBang (optional)
42+
43+
=== Set up your project
44+
45+
First, create a new Quarkus project with rest-client, qute and mcp server extension without default boilerplate code:
46+
47+
[source,bash]
48+
----
49+
quarkus create app --no-code -x rest-client-jackson,qute,mcp-server-stdio weather
50+
----
51+
52+
[NOTE]
53+
====
54+
We're using the `stdio` variant as it's required for MCP hosts that run the server as a subprocess. While an `sse` variant exists for Server-Sent Events streaming, we'll focus on the standard input/output approach.
55+
====
56+
57+
== Building the server
58+
59+
Create a new file `src/main/java/org/acme/Weather.java`. The complete code for this example is available here: [].
60+
61+
=== Weather API Integration
62+
63+
First, let's set up the REST client for the weather API:
64+
65+
[source,java]
66+
----
67+
@RegisterRestClient(baseUri = "https://api.weather.gov")
68+
public interface WeatherClient {
69+
// Get active alerts for a specific state
70+
@GET
71+
@Path("/alerts/active/area/{state}")
72+
Alerts getAlerts(@RestPath String state);
73+
74+
// Get point metadata for coordinates
75+
@GET
76+
@Path("/points/{latitude},{longitude}")
77+
JsonObject getPoints(@RestPath double latitude, @RestPath double longitude);
78+
79+
// Get detailed forecast using dynamically provided URL
80+
@GET
81+
@Path("/")
82+
Forecast getForecast(@Url String url);
83+
}
84+
----
85+
86+
To handle the API responses, we'll define some data classes. Note that we're only including the fields we need, as the complete API response contains much more data:
87+
88+
[source,java]
89+
----
90+
static record Period(
91+
String name,
92+
int temperature,
93+
String temperatureUnit,
94+
String windSpeed,
95+
String windDirection,
96+
String detailedForecast) {
97+
}
98+
99+
static record ForecastProperties(
100+
List<Period> periods) {
101+
}
102+
103+
static record Forecast(
104+
ForecastProperties properties) {
105+
}
106+
----
107+
108+
Since the Weather API uses redirects, add this to your `application.properties`:
109+
110+
[source,properties]
111+
----
112+
quarkus.rest-client.follow-redirects=true
113+
----
114+
115+
=== Formatting Helpers
116+
117+
We'll use Qute templates to format the weather data:
118+
119+
[source,java]
120+
----
121+
String formatForecast(Forecast forecast) {
122+
return forecast.properties().periods().stream().map(period -> {
123+
// Template for each forecast period
124+
return Qute.fmt(
125+
"""
126+
Temperature: {p.temperature}°{p.temperatureUnit}
127+
Wind: {p.windSpeed} {p.windDirection}
128+
Forecast: {p.detailedForecast}
129+
""",
130+
Map.of("p", period)).toString();
131+
}).collect(Collectors.joining("\n---\n"));
132+
}
133+
----
134+
135+
=== Implementing MCP Tools
136+
137+
Now let's implement the actual MCP tools. The `@Tool` annotation from `io.quarkiverse.mcp.server` marks methods as available tools, while `@ToolArg` describes the parameters:
138+
139+
[source,java]
140+
----
141+
@Tool(description = "Get weather alerts for a US state.")
142+
String getAlerts(@ToolArg(description = "Two-letter US state code (e.g. CA, NY)") String state) {
143+
return formatAlerts(weatherClient.getAlerts(state));
144+
}
145+
146+
@Tool(description = "Get weather forecast for a location.")
147+
String getForecast(
148+
@ToolArg(description = "Latitude of the location") double latitude,
149+
@ToolArg(description = "Longitude of the location") double longitude) {
150+
151+
// First get the point metadata which contains the forecast URL
152+
var points = weatherClient.getPoints(latitude, longitude);
153+
// Extract the forecast URL using Qute template
154+
var url = Qute.fmt("{p.properties.forecast}", Map.of("p", points));
155+
// Get and format the forecast
156+
return formatForecast(weatherClient.getForecast(url));
157+
}
158+
----
159+
160+
[NOTE]
161+
====
162+
The forecast API requires a two-step process where we first get point metadata and then use a URL from that response to fetch the actual forecast.
163+
====
164+
165+
== Running the Server
166+
167+
To simplify deployment and development, we'll package the server as an uber-jar and enable file logging (since stdio is reserved for the MCP protocol):
168+
169+
[source,properties]
170+
----
171+
quarkus.package.uber-jar=true
172+
quarkus.log.file.enable=true
173+
quarkus.log.file.path=weather-quarkus.log
174+
----
175+
176+
After running `mvn install`, you can use JBang to run the server using its Maven coordinates: `org.acme:weather:1.0.0-SNAPSHOT:runner`
177+
or manually using `java -jar target/weather-1.0.0-SNAPSHOT-runner.jar`.
178+
179+
=== Integration with Claude
180+
181+
Add this to your `claude_desktop_config.json`:
182+
183+
[source,json]
184+
----
185+
{
186+
"mcpServers": {
187+
"weather": {
188+
"command": "jbang",
189+
"args": ["--quiet",
190+
"org.acme:weather:1.0.0-SNAPSHOT:runner"]
191+
}
192+
}
193+
}
194+
----
195+
196+
The `--quiet` flag prevents JBang's output from interfering with the MCP protocol.
197+
198+
image::claude-tools.png[Claude Tools Integration]
199+
200+
[NOTE]
201+
====
202+
You can also run the server directly withou using java - then it would be something like `java -jar <FULL PATH>/weather-1.0.0-SNAPSHOT-runner.jar`. We use JBang here because simpler if you want to share with someone who does not want to build the MCP server locally.
203+
====
204+
205+
== Development Tools
206+
207+
=== MCP Inspector
208+
209+
For development and testing, you can use the MCP Inspector tool:
210+
211+
[source,bash]
212+
----
213+
npx @modelcontextprotocol/inspector
214+
----
215+
216+
This starts a local web server where you can test your MCP server:
217+
218+
image::mcp-inspector.png[MCP Inspector Interface]
219+
220+
=== Integration with LangChain4j
221+
222+
Since version 0.23.0, Quarkus LangChain4j supports MCP. For detailed information, see https://quarkus.io/blog/quarkus-langchain4j-mcp/[Using the Model Context Protocol with Quarkus+LangChain4j].
223+
224+
To use our weather server with LangChain4j, add this configuration:
225+
226+
[source,properties]
227+
----
228+
quarkus.langchain4j.mcp.weather.transport-type=stdio
229+
quarkus.langchain4j.mcp.weather.command=jbang,--quiet,org.acme:weather:1.0.0-SNAPSHOT:runner
230+
----
231+
232+
== Other Clients/MCP Hosts
233+
234+
The Model Context Protocol has a page listing https://modelcontextprotocol.io/clients[known clients].
235+
236+
While I have not tested all the various clients and MCP hosts, the similar approach of using `jbang --quiet <GAV>` should work for most if not all of them.
237+
238+
== Testing the Server
239+
240+
You can test the server through Claude or other MCP hosts with queries like:
241+
242+
* "What is the weather forecast for Solvang?"
243+
* "What are the weather alerts for New York?"
244+
245+
Here's what happens behind the scenes:
246+
247+
1. Your question goes to the LLM along with available tools information
248+
2. The LLM analyzes the question and determines which tools to use
249+
3. The client executes the selected tools via the MCP server
250+
4. Results return to the LLM
251+
5. The LLM formulates an answer using the tool results
252+
6. You see the final response!
253+
254+
== Conclusion
255+
256+
We've seen how Quarkus makes implementing an MCP server straightforward, requiring minimal boilerplate code compared to other implementations. The combination of Quarkus's extension system and JBang makes development and deployment quite a joy.
257+
258+
=== Further Reading
259+
260+
* https://modelcontextprotocol.io[Model Context Protocol Documentation]
261+
* https://docs.quarkiverse.io/quarkus-mcp-server/dev/[Quarkus MCP Extension Guide]
262+
* https://weather.gov/api[Weather API Documentation]
263+
* https://quarkus.io/blog/quarkus-langchain4j-mcp/[Using MCP with Quarkus+LangChain4j]
243 KB
Loading
66.8 KB
Loading
196 KB
Loading

0 commit comments

Comments
 (0)