|
| 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 ! |
0 commit comments