Skip to content

Commit 6da0f63

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

8 files changed

+227
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
---
2+
layout: post
3+
title: 'Getting ready for secure MCP with Quarkus MCP Server'
4+
date: 2025-04-24
5+
tags: ai mcp security
6+
synopsis: 'Explain how MCP clients can access secure Quarkus MCP SSE servers with access tokens'
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.
14+
15+
While it will take a bit of time for the MCP authorization part of the MCP specification be finalized and widely supported, one thing you can be certain about is that MCP authorization compliant clients will be able to login users with the OAuth2 authorization code flow and use bearer tokens to access MCP servers on their behalf.
16+
17+
In the meantime, you can use any MCP client that can accept a bearer access token and send it to the MCP server to start experimenting with MCP authorization.
18+
19+
In this post, we will create a https://github.com/quarkiverse/quarkus-mcp-server[Quarkus MCP SSE Server] that requires authentication for the main and tools SSE endpoints, and test it with `curl`, https://modelcontextprotocol.io/docs/tools/inspector[MCP inspector], and `Quarkus MCP SSE Server Dev UI`.
20+
21+
Please review the https://modelcontextprotocol.io/specification/2025-03-26[latest MCP specification] for a complete explanation of the MCP architecture, core concepts, transports.
22+
23+
== Create MCP SSE server
24+
25+
First, let's create a secure Quarkus MCP SSE server that requires authentication during the initial SSE handshake and the tool access.
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 during the initial handshake. 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.
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+
== Access MCP SSE server
108+
109+
[[mcp-server-devui]]
110+
=== Use Quarkus MCP Server Dev UI
111+
112+
Go to http://localhost:8080/q/dev[Dev UI], find both MCP Server and OpenId Connect cards:
113+
114+
image::mcp_server_oidc_devui.png[MCP Server and OIDC in DevUI,align="center"]
115+
116+
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.
117+
118+
[NOTE]
119+
====
120+
You can login to other providers such as `Auth0` or https://quarkus.io/guides/security-openid-connect-providers#github[GitHub] from OIDC DevUI as well. The only requirement is to update your 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 from Dev UI].
121+
122+
After logging in with `Keycloak` as `alice`, copy the acquired access token using a provided copy button:
123+
124+
image::login_and_copy_access_token.png[Login and copy access token,align="center"]
125+
126+
Next, go to the MCP Server card, select the `Tools` option and choose to `Call` the `user-name-provider` tool:
127+
128+
image::mcp_server_choose_tool.png[Choose MCP Server tool,align="center"]
129+
130+
Paste the copied access token into the Tool's `Bearer token` field, and request a new MCP SSE session:
131+
132+
image::mcp_server_bearer_token.png[MCP Server Bearer token,align="center"]
133+
134+
Use the `Call` action to make a tool call and get a response which contains the `alice` user name:
135+
136+
image::mcp_server_tool_response.png[MCP Server tool response,align="center"]
137+
138+
[[mcp-inspector]]
139+
=== Use MCP Inspector
140+
141+
Launch https://modelcontextprotocol.io/docs/tools/inspector[MCP inspector]:
142+
143+
[source,shell]
144+
----
145+
npx @modelcontextprotocol/inspector
146+
----
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+
Use this access token to have MCP inspector connect to the Quarkus MCP SSE server:
151+
152+
image::mcp_inspector_connect.png[MCP Inspector Connect,align="center"]
153+
154+
Next, make a `user-name-provider` tool call:
155+
156+
image::mcp_inspector_tool_call.png[MCP Inspector Tool Call,align="center"]
157+
158+
=== Use curl
159+
160+
Finally, let's use `curl` and also learn a little bit how the MCP protocol and MCP SSE transport work.
161+
162+
First, access the main SSE endpoint without the access token:
163+
164+
[source,shell]
165+
----
166+
curl -v localhost:8080/mcp/sse
167+
----
168+
169+
You will get HTTP 401 error.
170+
171+
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.
172+
173+
[source,shell]
174+
----
175+
curl -v -H "Authorization: Bearer ey..." localhost:8080/mcp/sse
176+
----
177+
178+
and get an SSE response such as:
179+
180+
[source,shell]
181+
----
182+
< content-type: text/event-stream
183+
<
184+
event: endpoint
185+
data: /messages/ZTZjZDE5MzItZDE1ZC00NzBjLTk0ZmYtYThiYTgwNzI1MGJ
186+
----
187+
188+
The current `curl` SSE session must stay open, for it to receive updates related to the dynamically allocated `data` endpoint.
189+
190+
Open another window and use the same access token to post a tool call request to the `data` endpoint:
191+
192+
[source,shell]
193+
----
194+
curl -v -H "Authorization: Bearer ey..." -H "Content-Type: application/json" --data @call.json http://localhost:8080/mcp/messages/ZTZjZDE5MzItZDE1ZC00NzBjLTk0ZmYtYThiYTgwNzI1MGJ
195+
----
196+
197+
where the `call.json` file looks like this:
198+
199+
[source,json]
200+
----
201+
{
202+
"jsonrpc": "2.0",
203+
"id": 2,
204+
"method": "tools/call",
205+
"params": {
206+
"name": "user-name-provider",
207+
"arguments": {
208+
}
209+
}
210+
}
211+
----
212+
213+
Now look in the first curl window and observe a response which contains the `alice` user name:
214+
215+
[source,shell]
216+
----
217+
event: message
218+
data: {"jsonrpc":"2.0","id":2,"result":{"isError":false,"content":[{"text":"alice","type":"text"}]}}
219+
----
220+
221+
== Conclusion
222+
223+
In this blog post, we have explained how you can easily create a secure Quarkus MCP SSE server, obtain a bearer access token and test it in
224+
225+
The Quarkus team is keeping an eye on the MCP Authorization specification evolution and working on having all possible MCP Authorization scenarios supported.
226+
227+
Stay tuned for more updates !
Loading
Loading
Loading
Loading
Loading
Loading
Loading

0 commit comments

Comments
 (0)