Skip to main content
Glama

NestJS MCP Server Module

by rekog-labs
MIT License
32,416
470
  • Apple
  • Linux
README.md9.55 kB
# Securing Your Remote MCP Tools - External Authorization Server Setup Claude, VS Code, Cursor, and other MCP clients can connect to your tools over HTTP. This means if you deploy an MCP Server somewhere publicly accessible, you can configure MCP Clients to reach it over HTTP. That's awesome - but *publicly accessible* also means anyone on the internet can hit your endpoints and either steal your data or increase your compute costs. Let's fix that by adding authentication in accordance with the MCP specification using an external authorization server. This approach keeps your MCP server focused on business logic while delegating all authentication concerns to a dedicated server. > **Note**: For a simpler setup with built-in OAuth support, see the [Built-in OAuth Module Guide](./built-in-oauth.md). This guide covers external authorization server setups for advanced scenarios. ### Try It Out For our setup you'll need Keycloak (an identity provider) and the [open-mcp-auth-proxy](https://github.com/wso2/open-mcp-auth-proxy) that handles the MCP-specific OAuth bits. First, let's get into the right directory where we'll set everything up: ```bash cd docs/oauth ``` **Step 1: Get the proxy** Download and set up the authentication proxy ([see releases for other distributions](https://github.com/wso2/open-mcp-auth-proxy/releases/)): ```bash wget https://github.com/wso2/open-mcp-auth-proxy/releases/download/v1.1.0/openmcpauthproxy_linux-v1.1.0.zip unzip openmcpauthproxy_linux-v1.1.0.zip mv ./openmcpauthproxy_linux-v1.1.0/openmcpauthproxy . rm -rf ./openmcpauthproxy_linux-v1.1.0 openmcpauthproxy_linux-v1.1.0.zip # cleanup ``` **Step 2: Start everything up** Now let's get all the services running: ```bash # Start Keycloak (this will wait until keycloak is fully ready) docker compose -f docker-compose.keycloak.yml up -d # Wait for keycloak to be ready until curl -s -f http://localhost:8080/realms/mcp/protocol/openid-connect/certs | jq -e '.keys | length > 0' > /dev/null 2>&1; do echo "Waiting for Keycloak JWKS to be ready..." sleep 2 done # Start the auth proxy ./openmcpauthproxy --debug # Start your MCP server (in another terminal from the root) npx ts-node-dev --respawn playground/servers/server-stateful.ts ``` **Step 3: Test it** Time to see the authentication in action! You have two options: ```bash # Option 1: Use the MCP Inspector npx @modelcontextprotocol/inspector@0.14.3 # Option 2: Run MCP client (in another terminal from the root) npx ts-node-dev --respawn playground/clients/http-sse-oauth-client.ts ``` Either option will walk you through the OAuth flow in your browser. The MCP client in the terminal then will let you run commands as follows: ``` mcp> call hello-world {"name": "joe"} 🔧 Tool 'hello-world' result: Hello, joe! Your user agent is: node ``` Cool, right? Your client just authenticated automatically without any manual setup. The diagram below shows how all the pieces fit together: ```mermaid sequenceDiagram Client->>Proxy: MCP Calls w/o Access Token Proxy-->>Client: Unauthorized Client->>Proxy: GET /.well-known/oauth-authorization-server Proxy->>Authorization Server: Forwarded w/ changes Authorization Server-->>Proxy: Authorization Server Metadata Proxy-->>Client: Authorization Server Metadata Client->>Proxy: POST /register Proxy->>Authorization Server: performs DCR Authorization Server-->>Proxy: Client Credentials Proxy-->>Client: Client Credentials Note left of Client: User authorizes Client->>Proxy: Token Request + code_verifier Proxy->>Authorization Server: verifies token Authorization Server-->>Proxy: Access Token (+ Refresh Token) Proxy-->>Client: Access Token (+ Refresh Token) Client->>Proxy: MCP Calls w/ Access Token Proxy->>MCP Server: Authorized Call ``` So the proxy handles: - Mapping the well-known endpoint `/.well-known/oauth-authorization-server` to the actual endpoint of the Authorization Server - Implementing specific Dynamic Client Registration for the supported IdPs - Reverse proxying other traffic to the Authorization Server (such as token verification etc.) And the MCP Server only concerns itself with tool calls. ## What Just Happened? (And How This Actually Works) When you ran that test, a bunch of interesting things happened behind the scenes. Let's break it down. ### The Core Problem We Just Solved You might be saying, "But isn't this what we always do when we authenticate and authorize traffic to services? Put a gateway (or proxy) in front of the services, and let the gateway handle those concerns?" **Yes, but there's a small twist.** Most auth systems expect you to know all your clients ahead of time. You register "MyApp v1.2" in your identity provider, get a client ID, and you're good to go. But with MCP? The clients don't know ahead of time which auth systems the MCP Servers will use for authentication. Each MCP Server can have a different auth server - they can be SaaS offerings, running on premises, and so on. To illustrate this with an absurd example. Do we expect the people at Claude.ai (or any MCP client for that matter) to go to every IdP server and register their client ahead of time? (That's impossible, but there is a solution!) The twist: **MCP clients need to authenticate on the fly!** ### How MCP Solves This The MCP spec uses OAuth with dynamic client registration. Here's what happened when you ran the test: 1. **Client shows up:** "Hi, I'm client X, I'd like to use this tool" 2. **Your auth server:** "OK, you're now registered as client #12345" 3. **User gets asked:** "This client wants to access YourTool, is that cool?" 4. **User approves:** "Yeah, that's fine" 5. **Client gets token:** Now the client can call your tool on behalf of that user Thus clients don't have to be registered ahead of time and everything happens automatically. ```mermaid sequenceDiagram participant C as MCP Client participant P as Auth Proxy participant A as Auth Server participant M as Your MCP Server C->>P: I want to use this tool P-->>C: You need to authenticate first C->>P: How do I register? P->>A: Register this new client A-->>P: Here's their client ID P-->>C: You're registered, now get user approval Note over C: User approves in browser C->>P: Here's my approval token P->>A: Is this token valid? A-->>P: Yep, all good P->>M: Forward the actual tool request M-->>P: Here's the result P-->>C: Tool response ``` This means the auth proxy sits in front of your MCP server and handles all the authentication dance. Your server just sees authenticated requests coming through. ### The Players in This Setup **Keycloak (Authorization Server):** This is what actually authenticates users and issues tokens. It's like the DMV - it knows who people are and can issue credentials. **Auth Proxy:** This translates between MCP clients and Keycloak. It handles the dynamic registration part (which most identity providers don't support out of the box) and forwards authenticated requests to your MCP server. **Your MCP Server (Resource Server):** This is your actual tool. It doesn't care about OAuth - it just responds to requests that the proxy forwards to it. **MCP Client:** Claude, VS Code, or whatever is trying to use your tool on behalf of a user. ### What Changed in the Latest MCP Spec The newer MCP spec (2025-06-18) allows the MCP server to tell clients "hey, don't talk to me about auth, talk to that other server over there." This means even cleaner separation. Your MCP server becomes purely about business logic. All the OAuth complexity lives elsewhere. In practice, you still need something to handle the dynamic client registration (since most identity providers have their own unique implementation not supported by all proxies). Using @rekog/mcp-nest, you don't have to do anything special to use the latest spec. You can just configure the Protected Resource Metadata well-known endpoint (which is what tells the clients how to interact with the resource server and where they find the authorization server) as shown below: ```typescript @Controller('.well-known') class WellKnownController { @Get('oauth-protected-resource') getOAuthProtectedResource() { return { resource: 'http://localhost:3030', authorization_servers: ['http://localhost:9090'], jwks_uri: 'http://localhost:8080/realms/mcp/protocol/openid-connect/certs', bearer_methods_supported: ['header', 'body', 'query'], scopes_supported: ['profile', 'offline_access'], resource_documentation: 'http://localhost:3030/docs', resource_policy_uri: 'http://localhost:3030/policy', resource_tos_uri: 'http://localhost:3030/tos', }; } } ``` And register it with Nest: ```typescript @Module({ imports: [ McpModule.forRoot({ name: 'playground-mcp-server', version: '0.0.1', streamableHttp: { enableJsonResponse: false, sessionIdGenerator: () => randomUUID(), statelessMode: false, }, }), ], controllers: [WellKnownController], // Registering it providers: [GreetingResource, GreetingTool, GreetingPrompt], }) ``` You can drop this in the file `playground/servers/server-stateful.ts` and try it out. The beauty is that once this is set up, it just works. New MCP clients can connect automatically, users control access through familiar OAuth consent screens, and your server stays focused on what it does best.

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rekog-labs/MCP-Nest'

If you have feedback or need assistance with the MCP directory API, please join our Discord server