Skip to content

Commit a3b0b6f

Browse files
committed
Add a blog post about secure MCP SSE server
1 parent 38a0ba6 commit a3b0b6f

6 files changed

+205
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
---
2+
layout: post
3+
title: 'Getting ready for secure MCP with Quarkus MCP Server'
4+
date: 2025-04-23
5+
tags: ai mcp security
6+
synopsis: 'Demonstrate secure Quarkus MCP SSE server access in dev mode'
7+
author: sberyozkin
8+
---
9+
:imagesdir: /assets/images/posts/secure_mcp_sse_server
10+
11+
== Introduction
12+
13+
https://modelcontextprotocol.io/specification/2025-03-26[The latest version of the Model Context Protocol (MCP) specification] introduces an https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization[authorization] flow for HTTP based MCP transports.
14+
15+
MCP authorization enables MCP SSE and Streamable HTTP clients to access MCP servers securely and is being very actively discussed and evaluated right now. This https://github.com/modelcontextprotocol/modelcontextprotocol/pull/284[MCP authorization PR] is one of the focal points for the MCP authorization discussions.
16+
17+
While it will take a bit of time for the MCP authorization specification be finalized and widely supported, one thing you can be certain about is that secure MCP servers that wish to interoperate with MCP authorization aware MCP clients will be required to accept and verify bearer access tokens sent by these clients on behalf of their users.
18+
19+
The https://github.com/quarkiverse/quarkus-mcp-server[Quarkus MCP Server project] is ready for you to start experimenting with secure MCP SSE servers.
20+
21+
In this post, we will show how Quarkus provides a complete Dev UI experience for you to login with an OpenId Connect or OAuth2 provider of your choice, and use an acquired access token to access MCP SSE server securely.
22+
23+
== Create MCP SSE server
24+
25+
First, let's create a secure Quarkus MCP SSE server.
26+
27+
You can find the complete project source https://github.com/quarkiverse/quarkus-mcp-server/tree/main/samples/secure-mcp-sse-server[here].
28+
29+
=== Maven dependencies
30+
31+
Add the following dependencies:
32+
33+
[source,xml]
34+
----
35+
<dependency>
36+
<groupId>io.quarkiverse.mcp</groupId>
37+
<artifactId>quarkus-mcp-server-sse</artifactId> <1>
38+
<version>1.1.1</version>
39+
</dependency>
40+
41+
<dependency>
42+
<groupId>io.quarkus</groupId>
43+
<artifactId>quarkus-oidc</artifactId> <2>
44+
</dependency>
45+
----
46+
<1> `quarkus-mcp-server-sse` is required to support the MCP SSE transport.
47+
<2> `quarkus-oidc` is required to secure access to the MCP SSE endpoints.
48+
49+
[[tool]]
50+
=== Tool
51+
52+
Let's create a tool that can be invoked only if the current MCP request is authenticated:
53+
54+
[source,java]
55+
----
56+
package org.acme;
57+
58+
import io.quarkiverse.mcp.server.TextContent;
59+
import io.quarkiverse.mcp.server.Tool;
60+
import io.quarkus.security.Authenticated;
61+
import io.quarkus.security.identity.SecurityIdentity;
62+
import jakarta.inject.Inject;
63+
64+
public class ServerFeatures {
65+
66+
@Inject
67+
SecurityIdentity identity;
68+
69+
@Tool(name = "user-name-provider", description = "Provides a name of the current user") <1>
70+
@Authenticated <2>
71+
TextContent provideUserName() {
72+
return new TextContent(identity.getPrincipal().getName()); <3>
73+
}
74+
}
75+
----
76+
<1> Provide a tool that can return a name of the current user. Note the `user-name-provider` tool name, you will use it later for a tool call.
77+
<2> Require authenticated tool access. See also how the main MCP SSE endpoint is secured in the <<configuration>> section below.
78+
<3> Use the injected `SecurityIdentity` to return the current user's name.
79+
80+
[[configuration]]
81+
=== Configuration
82+
83+
Finally, let's configure our secure MCP server:
84+
85+
[source,properties]
86+
----
87+
quarkus.http.auth.permission.authenticated.paths=/mcp/sse
88+
quarkus.http.auth.permission.authenticated.policy=authenticated
89+
----
90+
<1> Enforce an authenticated access to the main MCP SSE endpoint. See also how the tool is secured with an annotation in the <<tool>> section above, though you can also secure access to the tool by listing both the main and tools endpoints in the configuration, for example: `quarkus.http.auth.permission.authenticated.paths=/mcp/sse,/mcp/messages/*`.
91+
92+
The OIDC configuration is provided in devmode by https://quarkus.io/guides/security-openid-connect-dev-services[Dev Services for Keycloak]. It creates a default realm, client and adds two users, `alice` and `bob`, for you to get started immediately. You can also register a custom Keycloak realm to work with the the existing realm, client and user registrations.
93+
94+
No problems if you do not work with Keycloak, see the <<mcp-server-devui>> section for more details.
95+
96+
We are ready to test our secure MCP server, both in DevUI and with `curl`.
97+
98+
== Start MCP SSE server
99+
100+
Start the server in the dev mode:
101+
102+
[source,shell]
103+
----
104+
mvn quarkus:dev
105+
----
106+
107+
[[mcp-server-devui]]
108+
=== Access MCP SSE server in DevUI
109+
110+
Go to http://localhost:8080/q/dev[Dev UI], find both MCP Server and OpenId Connect cards:
111+
112+
image::mcp_server_oidc_devui.png[MCP Server and OIDC in DevUI,align="center"]
113+
114+
Select an OpenId Connect card and https://quarkus.io/guides/security-openid-connect-dev-services#develop-service-applications[login to Keycloak] using an `alice` name and an `alice` password.
115+
116+
What about other providers such as `Auth0` or https://quarkus.io/guides/security-openid-connect-providers#github[GitHub] ?
117+
In this case, you can login to them from OIDC DevUI as well, the only requirement is to update your provider application registration to allow callbacks to DevUI. For example, see how you can https://quarkus.io/guides/security-oidc-auth0-tutorial#looking-at-auth0-tokens-in-the-oidc-dev-ui[login to Auth0].
118+
119+
After logging in with `Keycloak` as `alice`, copy the acquired access token using a provided copy button:
120+
121+
image::login_and_copy_access_token.png[Login and copy access token,align="center"]
122+
123+
Now go the MCP Server card, select the `Tools` option and choose the `user-name-provider` tool:
124+
125+
image::mcp_server_choose_tool.png[Choose MCP Server tool,align="center"]
126+
127+
Paste the copied access token into the Tool's `Bearer token` field, and request a new MCP SSE session:
128+
129+
image::mcp_server_bearer_token.png[MCP Server Bearer token,align="center"]
130+
131+
Make a tool call by selecting the `Call` action and get a response which contains the `alice` user name:
132+
133+
image::mcp_server_tool_response.png[MCP Server tool response,align="center"]
134+
135+
=== Access MCP SSE server with curl
136+
137+
Let's try to use `curl` as well and also learn a little bit how MCP SSE transport works.
138+
139+
Access the main SSE endpoint without the access token first:
140+
141+
[source,shell]
142+
----
143+
curl -v localhost:8080/mcp/sse
144+
----
145+
146+
You will get HTTP 401 error.
147+
148+
Next, get and copy the access token as explained in the <<mcp-server-devui>> section above. You can use the same access token that you may have already used when working through that section.
149+
150+
[source,shell]
151+
----
152+
curl -v -H "Authorization: Bearer ey..." localhost:8080/mcp/sse
153+
----
154+
155+
and get an SSE response such as:
156+
157+
[source,shell]
158+
----
159+
< content-type: text/event-stream
160+
<
161+
event: endpoint
162+
data: /messages/ZTZjZDE5MzItZDE1ZC00NzBjLTk0ZmYtYThiYTgwNzI1MGJ
163+
----
164+
165+
The current `curl` SSE session must stay open, for it to receive updates related to the dynamically allocated `data` endpoint.
166+
167+
Open another window and use the same access token to post the tool call request to the `data` endpoint:
168+
169+
[source,shell]
170+
----
171+
url -v -H "Authorization: Bearer ey..." -H "Content-Type: application/json" --data @call.json http://localhost:8080/mcp/messages/ZTZjZDE5MzItZDE1ZC00NzBjLTk0ZmYtYThiYTgwNzI1MGJ
172+
----
173+
174+
where a `call.json` looks like this:
175+
176+
[source,json]
177+
----
178+
{
179+
"jsonrpc": "2.0",
180+
"id": 2,
181+
"method": "tools/call",
182+
"params": {
183+
"name": "user-name-provider",
184+
"arguments": {
185+
}
186+
}
187+
}
188+
----
189+
190+
Now look in the first curl window and observe a response such as:
191+
192+
[source,shell]
193+
----
194+
event: message
195+
data: {"jsonrpc":"2.0","id":2,"result":{"isError":false,"content":[{"text":"alice","type":"text"}]}}
196+
----
197+
198+
== Conclusion
199+
200+
In the this blog post, we have explained how a secure Quarkus MCP SSE server can be created and tested in Quarkus Dev UI and with the `curl` tool.
201+
Please give it a try as well.
202+
203+
The Quarkus team is keeping an eye on the MCP Authorization specification evolution and working on having all possible MCP Authorization scenarios supported.
204+
205+
Stay tuned for more updates !
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)