# Model Context Protocol (MCP) Node Example

This guide will walk you through how to implement [MCP authentication](https://docs.propelauth.com/mcp-authentication/overview) in Node using PropelAuth's MCP support. We'll be using the popular [mcp-use](https://mcp-use.com/docs/typescript/getting-started/welcome) framework to create a simple MCP server that authenticates users using PropelAuth. We'll also create a tool in the MCP server that returns the logged in user's information.

By the end of this guide you'll have a working MCP server that you can use with the majority of AI services, such as Cursor:

## [Enabling and Configuring MCP Authentication](https://docs.propelauth.com/mcp-authentication/examples/node#enabling-and-configuring-mcp-authentication)

Begin by navigating to the **MCP** page in the PropelAuth Dashboard and enabling MCP authentication. Once enabled globally, you'll be able to enable it for any of your environments, such as test, staging, or prod.

The next step is to add a **Redirect URI** to the **Allowed MCP Clients**. This is the URL that your users will be redirected to after successfully logging in. We include several templates for the most common AI tools, including Claude Desktop, ChatGPT, and Cursor. In this guide we'll be using the template for Cursor.

Depending on the AI tool you're using you may need to configure additional settings. For example, if you're using Claude Code, you'll need to enable [Dynamic Client Registration](https://docs.propelauth.com/mcp-authentication/overview#allowing-dynamic-client-registration).

### Creating Scopes

Let's move onto scopes. With MCP auth, scopes define the specific permissions and access levels granted to an AI service, allowing servers to restrict what resources or operations the AI service can access. PropelAuth offers two types of scopes: **User Scopes** and **Organization Scopes**. Check out the docs [here](https://docs.propelauth.com/mcp-authentication/overview#configuring-mcp-client-scopes) for more information on org scopes and user scopes.

For this example we'll create a user scope called `read:user_data`. We can add this scope by navigating to the **User Scopes** section of the **MCP** page and clicking **Add Scope**.

### Creating Request Validation Credentials

The last step before we start coding is to generate credentials for request validation. These credentials are used in the [Introspection Endpoint](https://docs.propelauth.com/mcp-authentication/overview#allowing-dynamic-client-registration) to verify your user's access tokens that are sent by the AI tool to your MCP server. See the documentation [here](https://docs.propelauth.com/mcp-authentication/overview#validating-user-access-tokens) for more information.

Generate a new set of credentials by clicking the **Generate Credentials** button. Make sure to save the **Client ID** and **Client Secret** for the next step.

## [Creating a Node MCP Server](https://docs.propelauth.com/mcp-authentication/examples/node#creating-a-node-mcp-server)

Let's create a simple MCP server. We'll use the [mcp-use](https://mcp-use.com/docs/typescript/getting-started/welcome) framework for this example. Begin by installing mcp-use:

```bash
npm i mcp-use
```

We'll be using mcp-use's built in [oauthCustomProvider](https://mcp-use.com/docs/typescript/server/authentication/providers/custom) to do most of the heavy lifting for us. All we have to do is configure it with the necessary information, such as our **Auth URL** (found in the **Backend Integration** page of the PropelAuth Dashboard), the **Client ID** and **Client Secret** that we generated in the previous step, and the **Scopes** that we created earlier.

We also need to include the URL of the MCP server. In this example we'll be using `http://localhost:8000` when running the MCP server locally.

Using localhost as the MCP server is not compatible with each AI service, such as Claude or ChatGPT. Consider using ngrok when testing locally to expose your MCP server to the internet.

```ts
import { MCPServer, oauthCustomProvider } from 'mcp-use/server'

const REQUEST_VALIDATION_CLIENT_ID = "VALIDATION_CLIENT_ID";
const REQUEST_VALIDATION_CLIENT_SECRET = "VALIDATION_CLIENT_SECRET";
const PROPELAUTH_AUTH_URL = "PROPELAUTH_AUTH_URL";
const MCP_SERVER_URL = "http://localhost:8000"
const SCOPES = ["read:user_data"]

async function validateToken(token: string) {
  if (token.startsWith('Bearer ')) {
    token = token.substring(7);
  }
  const credentials = btoa(`${REQUEST_VALIDATION_CLIENT_ID}:${REQUEST_VALIDATION_CLIENT_SECRET}`);
  const response = await fetch(`${PROPELAUTH_AUTH_URL}/oauth/2.1/introspect`, {
    method: "POST",
    headers: {
      "Authorization": `Basic ${credentials}`,
      "Content-Type": "application/x-www-form-urlencoded"
    },
    body: new URLSearchParams({ "token": `${token}` })
  });

if (!response.ok) {
    throw new Error(`Introspection failed: ${response.statusText}`);
  }

const responseBody = await response.json();
  if (responseBody.active === false) {
    throw new Error(`Token is not active`);
  }
  return {
    payload: responseBody
  }
}

const server = new MCPServer({
  name: 'my-secure-server',
  version: '1.0.0',
  baseUrl: MCP_SERVER_URL + '/mcp',
  oauth: oauthCustomProvider({
    issuer: PROPELAUTH_AUTH_URL,
    jwksUrl: `${PROPELAUTH_AUTH_URL}/.well-known/jwks.json`,
    authEndpoint: `${PROPELAUTH_AUTH_URL}/oauth/2.1/authorize`,
    tokenEndpoint: `${PROPELAUTH_AUTH_URL}/oauth/2.1/token`,
    scopesSupported: SCOPES,
    grantTypesSupported: ['authorization_code', 'refresh_token'],
    verifyToken: async (token) => {
      // Custom verification logic
      const user = await validateToken(token);
      return user;
    },
  })
})

server.app.get("/.well-known/oauth-authorization-server", async (c) => {
    const response = await fetch(PROPELAUTH_AUTH_URL + "/.well-known/oauth-authorization-server/oauth/2.1");
    return c.json(await response.json());
});

["/mcp/.well-known/oauth-protected-resource", "/.well-known/oauth-protected-resource/mcp"].forEach(path => {
  server.app.get(path, (c) => c.json({
      resource: MCP_SERVER_URL + "/mcp",
      authorization_servers: [PROPELAUTH_AUTH_URL + "/oauth/2.1"],
      scopes_supported: SCOPES,
  }));
});

await server.listen(8000)
```

Above, we define a validateToken function that is used to automatically make requests to the [Introspection Endpoint](https://docs.propelauth.com/reference/api/mcp#introspection-endpoint) to validate user tokens.

And that's it! We have successfully set up our MCP server with authentication. But let's create a client and hook it up to Cursor to test it out.

## [Creating an MCP OAuth Client](https://docs.propelauth.com/mcp-authentication/examples/node#creating-an-mcp-o-auth-client)

In a [previous step](https://docs.propelauth.com/mcp-authentication/examples/node#enabling-and-configuring-mcp-authentication) we set an available **Redirect URI** that can be used for MCP clients. Now we have to make the client itself by navigating to the **OAuth Clients** section of the MCP dashboard and clicking **Create Client**.

Select the **Redirect URI** that we added earlier, set **Client Type** to `public`, and select **Create Client**.

You'll then get back a **Client ID** and **Client Secret**. We'll use these when installing our MCP client in Cursor.

## [Installing the MCP Client in Cursor](https://docs.propelauth.com/mcp-authentication/examples/node#installing-the-mcp-client-in-cursor)

Now that we have our MCP client set up, we can install it in Cursor. Navigate to the **Tools & MCP** section of the Cursor app and click **Add Custom MCP**.

This will open a `mcp.json` file. We'll use this file to configure our MCP client. Use the following template and replace the placeholders with your actual **Client ID** and **Client Secret**.

```json
{
  "mcpServers": {
    "my_mcp_server": {
      "url": "http://localhost:8000/mcp",
      "auth": {
        "CLIENT_ID": "{YOUR_CLIENT_ID}",
        "CLIENT_SECRET": "{YOUR_CLIENT_SECRET}",
        "scopes": ["read:user_data"]
      }
    }
  }
}
```

Save your changes and navigate back to the **Tools & MCP** section. A MCP server will now be listed. Click the **Connect** button and proceed through the authentication process by logging in. You should then see a page to authorize access to the requested scope that we created earlier:

And you're now logged in! Let's now create a tool in our MCP server to return the authenticated user's data.

## [Getting the Authenticated User's Information](https://docs.propelauth.com/mcp-authentication/examples/node#getting-the-authenticated-users-information)

The mcp-use library includes a built-in [User Context](https://mcp-use.com/docs/typescript/server/authentication/user-context) that can be used to retrieve the authenticated user's information. We can use this to create a tool called `who_am_i` in our MCP server.

```ts
server.tool({
  name: 'who_am_i',
  description: 'Get the authenticated user profile',
  cb: async (params, context) => {
    // Access authenticated user info
    const user = context.auth.payload
    return {
      content: [{ type: "text", text: JSON.stringify(user) }]
    };
  }
})
```

When we connect to our MCP server in Cursor, we can call the `who_am_i` tool to retrieve the authenticated user's information.

If you require more information about the user, such as their organization membership, you can use the [Fetch User By User ID API](https://docs.propelauth.com/reference/api/user#fetch-user-by-user-id) by passing the `sub` value from the token response as the `userId` parameter.

And that's it! You've now added authentication to your MCP server and created a tool that returns the authenticated user's information.

If you have any questions or need further assistance, feel free to reach out to our support team at [support@propelauth.com](mailto:support@propelauth.com).
