Logo
Agentailor
Published on

Securing MCP Servers: A Practical Guide with Keycloak (using create-mcp-server)

DocuMentor AI logo

Short on time?

Get the key takeaways in under 10 seconds with DocuMentor AI, my Chrome extension. No account required.

Try it here →
Authors
  • avatar
    Name
    Ali Ibrahim
    Twitter
OAuth for MCP Servers Banner

Introduction

MCP servers are powerful. They let AI agents interact with databases, APIs, file systems, and virtually anything you can imagine. But there's a catch: most tutorials show you how to build MCP servers without authentication.

That's fine for local development. It's a problem for production.

An unsecured MCP server is an open door. Anyone who discovers your endpoint can invoke your tools, access your resources, and potentially wreak havoc on your systems. As MCP adoption grows and servers move from localhost to cloud deployments, security isn't optional anymore.

The good news? The MCP Authorization specification provides a standard way to secure MCP servers using OAuth 2.1. And with the right tools, implementing it is straightforward.

In this guide, you'll learn:

  • How MCP authorization works (without the jargon)
  • How to scaffold a secure MCP server with create-mcp-server
  • How to set up Keycloak as your OIDC provider
  • How to test your authenticated server with VS Code, Cursor and a terminal client

Let's lock down your MCP server.

Understanding MCP Authorization

If you're new to MCP, here's the short version: the Model Context Protocol is an open standard that lets AI assistants discover and use external tools. Think of it as a universal adapter between AI models and the real world. For a deeper dive, check out our article on running MCP servers with Docker.

Why Security Matters

When you deploy an MCP server, you're exposing capabilities to the network. Those capabilities might include:

  • Reading and writing to databases
  • Sending emails or notifications
  • Accessing internal APIs
  • Managing cloud resources

Without authentication, anyone can call these tools. That's why the MCP specification includes authorization as a core feature.

OAuth: The Hotel Key Card

OAuth might sound intimidating, but the concept is simple. Think of it like a hotel key card system:

  1. You check in at the front desk (authentication)
  2. You receive a key card (access token)
  3. You use the key card to access your room (authorized requests)
  4. The door checks if your card is valid (token validation)

That's OAuth in a nutshell. The MCP client gets a token from an authorization server, then includes that token with every request. The MCP server validates the token before granting access.

The MCP specification requires OAuth 2.1 with PKCE (Proof Key for Code Exchange), which adds an extra security layer to prevent token interception. You don't need to understand the cryptographic details, just know that it's a modern, secure approach.

Dynamic Client Registration (DCR)

Here's something important that often gets overlooked: Dynamic Client Registration.

In traditional OAuth setups, you manually register each client application with your authorization server. You create a client, get a client ID and secret, and configure them in your app. This works fine when you have a handful of known clients.

But MCP is different. The whole point is that any MCP-compatible client should be able to connect to your server. Claude Desktop, VS Code, Cursor, custom agents, there could be dozens of different clients trying to connect.

DCR solves this. It allows clients to register themselves automatically with the authorization server. No manual setup required. The client says "hey, I'd like to connect," and the server says "here are your credentials."

This is critical for the MCP ecosystem to scale. And it's one of the main reasons we're using Keycloak: Keycloak fully supports Dynamic Client Registration. Not all OIDC providers do. If you're evaluating alternatives like Auth0, Azure AD, or Okta, check their DCR support carefully.

For the complete technical specification, see the MCP Authorization documentation.

Introducing create-mcp-server

Building an MCP server from scratch with OAuth is tedious. You need to set up Express, configure middleware, handle token validation, manage sessions, and wire up SSE for real-time updates. That's hours of boilerplate before you write a single tool.

create-mcp-server eliminates that friction. It's a CLI tool that scaffolds production-ready MCP servers in seconds:

npx @agentailor/create-mcp-server

The CLI walks you through a few questions and generates a complete project with TypeScript, Express.js, and optionally OAuth authentication baked in.

Two Templates

FeatureStatelessStateful
Session management
SSE support
OAuth option
EndpointsPOST /mcpPOST, GET, DELETE /mcp

Stateless: Each request creates a new transport instance. Simple, but no session persistence.

Stateful: Sessions are maintained across requests. Supports Server-Sent Events for real-time updates. This is what you need for OAuth.

For this guide, we'll use the Stateful template with OAuth enabled.

Scaffolding Your Secure MCP Server

Let's create our server. Run the CLI and answer the prompts:

npx @agentailor/create-mcp-server

When prompted:

  1. Enter (y) for npx to download the package
  2. Project name: my-secure-mcp-server
  3. Template: Stateful
  4. Enable OAuth: Yes
  5. Package manager: Your preference (npm, pnpm, or yarn) The CLI generates this structure:
my-secure-mcp-server/
├── src/
│   ├── server.ts     # MCP server (tools, prompts, resources)
│   ├── index.ts      # Express app and transport setup
│   └── auth.ts       # OAuth middleware
├── package.json
├── tsconfig.json
├── .gitignore
├── .env.example
└── README.md

Key Files Explained

src/server.ts: This is where you define your MCP tools, prompts, and resources. It's the "business logic" of your server.

src/index.ts: The Express application. It sets up routes, applies middleware, and manages the HTTP transport.

src/auth.ts: The OAuth middleware. This is provider-agnostic, it works with any OIDC-compliant authorization server. You configure the provider through environment variables.

.env.example: Template for required environment variables:

PORT=3000

# OAuth Configuration
# Issuer URL - your OAuth provider's base URL
# Examples:
#   Auth0: https://your-tenant.auth0.com
#   Keycloak: http://localhost:8080/realms/your-realm
OAUTH_ISSUER_URL=https://your-oauth-provider.com

# Audience - the API identifier (optional, but recommended)
# This should match the "aud" claim in your JWT tokens
OAUTH_AUDIENCE=https://your-mcp-server.com

Install dependencies and you're ready to configure Keycloak:

cd my-secure-mcp-server
npm install

Setting Up Keycloak

We're using Keycloak as our OIDC provider. Here's why:

  • Open-source: No vendor lock-in, full transparency
  • OIDC-compliant: Works with any OAuth 2.1 / OpenID Connect client
  • Supports DCR: Dynamic Client Registration out of the box
  • Self-hosted: Complete control over your auth infrastructure
  • Battle-tested: Used by enterprises worldwide

Running Keycloak with Docker

From your terminal, run the following command to start the Keycloak container:

docker run -p 127.0.0.1:8080:8080 -e KC_BOOTSTRAP_ADMIN_USERNAME=admin -e KC_BOOTSTRAP_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak start-dev

Wait a minute for startup, then access the admin console at http://localhost:8080.

Configuring Keycloak

Log in with admin / admin, then follow these steps: Login Screen

1. Create a Realm

  1. Click on "Manage realms" in the top-left sidebar
  2. Click Create realm
  3. Name it mcp-realm
  4. Click Create
Realm Creation

2. Create a Client

  1. Go to ClientsCreate client
  2. Client ID: mcp-server-client
  3. Client authentication: Enable (this makes it confidential)
  4. Leave redirect URIs empty (we'll update later if needed)
  5. Click Save
Client Creation

Environment Variables

Update/create your .env file with the Keycloak values:

PORT=3000

OAUTH_ISSUER_URL=http://localhost:8080/realms/mcp-realm
OAUTH_AUDIENCE= #leave empty for Keycloak

3. Create a Test User

  1. Go to UsersAdd user
  2. Username: testuser
  3. Set Email verified to ON
  4. Click Create
  5. Go to Credentials tab → Set password
  6. Enter a password and disable "Temporary"
User Creation

Connecting MCP Server to Keycloak

With Keycloak running and your .env configured, start your MCP server:

npm run dev

You should see output like:

[auth] Validating OAuth configuration for issuer: http://localhost:8080/realms/mcp-realm
[auth] Successfully fetched OIDC discovery document
[auth] Authorization endpoint: http://localhost:8080/realms/mcp-realm/protocol/openid-connect/auth
[auth] Token endpoint: http://localhost:8080/realms/mcp-realm/protocol/openid-connect/token
[auth] JWKS URI: http://localhost:8080/realms/mcp-realm/protocol/openid-connect/certs
[auth] JWKS endpoint is accessible
[auth] OAuth configuration validated successfully
MCP Stateful HTTP Server listening on port 3000
OAuth metadata available at http://localhost:3000/.well-known/oauth-protected-resource

The auth middleware automatically:

  1. Intercepts incoming requests
  2. Extracts the Bearer token from the Authorization header
  3. Validates the token against Keycloak's JWKS endpoint
  4. Rejects requests with invalid or missing tokens

Your server is now protected. Unauthenticated requests will receive a 401 Unauthorized response.

Testing Your Secure MCP Server

Let's verify everything works. We'll test with three methods: VS Code integration, Cursor integration, and a terminal client.

Setting Redirect URIs for VS Code and Cursor

  1. In Keycloak, go to Clientsmcp-server-clientSettings
  2. Under Valid Redirect URIs, add the following URIs:
cursor://anysphere.cursor-mcp/oauth/callback
https://vscode.dev/redirect/*
http://127.0.0.1:33418/* # this may vary based on your setup
  1. Click Save
Redirect URIs

Note: If you have an error of "Invalid redirect URI", double-check the URIs match exactly or add missing ones.

VS Code Integration

VS Code supports MCP servers with OAuth authentication. Create a .vscode/mcp.json file in your project:

{
  "servers": {
    "my-secure-mcp-server": {
      "type": "http",
      "url": "http://localhost:3000/mcp"
    }
  }
}

On top of the server name you'll see "Start":

  1. VS Code will try to connect to your MCP server
  2. If it detects OAuth, it initiates the authorization flow
  3. If DCR is supported, it registers the client dynamically, otherwise it will prompt you to enter client details
  4. You log in with your Keycloak credentials
  5. VS Code receives the token and connects to your server

Your MCP tools are now available through VS Code's Copilot.

Cursor Integration

Cursor also supports MCP with OAuth. In Cursor, add a new MCP server:

  1. Go to Cursor settings → Tools & MCP → New MCP Server

  2. Enter the following details:

{
  "mcpServers": {
    "oauth-server": {
      "url": "http://localhost:3000/mcp",
      "auth": {
        "CLIENT_ID": "mcp-server-client",
        "CLIENT_SECRET": "<your-client-secret | EMPTY it's optional>",
        "scopes": ["mcp:tools"]
      }
    }
  }
}
  1. After saving, go back to Tools & MCP and click on "Connect" next to your new server.
  2. Cursor will open a browser window for you to log in via Keycloak.
  3. After logging in, Cursor will receive the access token and connect to your MCP server.
MCP Tools

Terminal Client Testing

The terminal client uses Dynamic Client Registration (DCR) to connect to your MCP server. This requires additional Keycloak configuration that wasn't needed for VS Code or Cursor (which use predefined clients).

Enabling Dynamic Client Registration in Keycloak

For DCR to work, Keycloak needs to trust the host where your client is running:

  1. In Keycloak, go to ClientsClient registrationTrusted Hosts
  2. Disable the Client URIs Must Match setting
  3. Add your testing host's IP address to the trusted hosts list

To find your host IP:

  • Linux/macOS: Run ifconfig in your terminal
  • Windows: Run ipconfig in Command Prompt

If you're unsure which IP to add, check the Keycloak logs for a line like Failed to verify remote host : 192.168.x.x. That's the IP you need to whitelist.

DCR Trusted Hosts Configuration

Creating the mcp:tools Scope

The terminal client requires a custom scope to access MCP tools. Without this, authentication will succeed but tool access will fail.

  1. In Keycloak, go to Client scopesCreate client scope
  2. Name: mcp:tools
  3. Type: Set to Default (so it's automatically included)
  4. Include in token scope: Enable this toggle (required for token validation)
  5. Click Save
Create mcp:tools Scope

Running the Terminal Client

Now you can test with the mcp-oauth-client:

git clone https://github.com/IBJunior/mcp-oauth-client
cd mcp-oauth-client
npm install
npm run build

Configure the client with your server and Keycloak details, then run:

npm run dev

The client will:

  1. Use DCR to register itself with Keycloak automatically
  2. Perform the OAuth flow
  3. Obtain an access token (if the browser doesn't open automatically, copy-paste the URL from the console)
Simple MCP Client
  1. Connect to your MCP server
  2. List available tools
  3. Allow you to invoke tools interactively

This is useful for debugging and verifying your setup works end-to-end.

MCP Tools

Adding Custom Tools

The scaffolded server comes with example tools. Let's add a custom one to see how easy it is.

Open src/server.ts and add a new tool:

server.registerTool(
  'greet',
  {
    description: 'Greets a user in their preferred language.',
    inputSchema: {
      name: z.string().describe("The user's name"),
      language: z.enum(['en', 'es', 'fr']).optional().describe('Greeting language'),
    },
  },
  async ({ name, language = 'en' }) => {
    const greetings = {
      en: `Hello, ${name}! Welcome to the secure MCP server.`,
      es: `¡Hola, ${name}! Bienvenido al servidor MCP seguro.`,
      fr: `Bonjour, ${name}! Bienvenue sur le serveur MCP sécurisé.`,
    }

    return {
      content: [
        {
          type: 'text',
          text: greetings[language],
        },
      ],
    }
  }
)

That's it. Restart your server and the new tool is available, automatically protected by OAuth. No additional authentication code required per tool. The middleware handles everything at the request level.

Using MCP Inspector

MCP Inspector is a debugging UI for MCP servers. It lets you explore available tools, test invocations, and inspect responses.

The scaffolded project includes an inspect script:

npm run inspect

Important caveat: MCP Inspector currently only works with public (unauthenticated) servers. It doesn't support OAuth flows.

If you need to use the Inspector:

  1. Temporarily disable OAuth in your server (comment out the auth middleware)
  2. Run the Inspector
  3. Re-enable OAuth when done

For authenticated testing, use the terminal client described above, or write integration tests that handle the OAuth flow programmatically.

Recap

Let's summarize what we've covered:

WhatWhy
MCP AuthorizationIndustry standard for securing MCP servers
OAuth 2.1 + PKCEModern, secure token-based authentication
Dynamic Client RegistrationAllows any MCP client to connect without manual setup
KeycloakOpen-source, OIDC-compliant, supports DCR
create-mcp-serverFast scaffolding with authentication built-in
OIDC abstractionSwap providers via environment variables

The key insight: security doesn't have to be complicated. With the right tools and a clear understanding of the concepts, you can go from zero to a production-ready, authenticated MCP server in minutes.

Conclusion

You've built a production-ready MCP server with OAuth authentication. Your tools are protected, tokens are validated, and you're following the MCP Authorization specification.

But here's the best part: Keycloak is just one option.

Because create-mcp-server uses a provider-agnostic OIDC implementation, you can swap Keycloak for any compliant provider. Just update your environment variables and you're done. That's the power of standards-based authentication.

A note on production deployments: The Keycloak configuration in this guide is designed for demonstration purposes. A production setup requires additional hardening: HTTPS everywhere, stricter redirect URI validation, token lifetime tuning, proper realm and client policies, and more. For production-grade configuration, refer to the official Keycloak documentation.

Next Steps

Enjoying content like this? Sign up for Agent Briefings, where I share insights and news on building and scaling MCP Servers and AI agents.

Resources

Agent Briefings

Level up your agent-building skills with weekly deep dives on MCP, prompting, tools, and production patterns.