Protect an MCP Server with OAuth

Protect an Express MCP server with Neon OAuth sign-in and scoped tokens.

Introduction

30 Second Summary

An AI assistant becomes more useful when it can reach private tools. That access needs a clear checkpoint so the person stays in control.

In this project, you will build an Express MCP server that requires a valid token before it returns a tool result. You will use Neon's Managed Better Auth to send an MCP client through login and consent before access is granted.

What You'll Build

When you finish, an MCP client guides you through login and consent before the protected tool returns its result.

By the end of this project, you'll have:

  • A protected MCP tool that returns its result for a signed-in user while showing a 401 response when no valid token is present.
  • A login and consent flow that shows each page in sequence before the client receives permission.
  • A branch isolation check that rejects a preview-branch token from the server pinned to your main branch.
  • Secret Mission: An optional challenge to push your skills further.

Are there any prerequisites?

A free Neon account is enough for this project. You'll also need Node.js plus basic familiarity with Express.

Before We Start

Your protected MCP server needs a reliable local toolkit before Neon can handle sign-in. A missing runtime can make an authorization failure look like a server bug.

You will prepare every prerequisite this project depends on. You will finish by launching MCP Inspector as proof that the client side is ready.

In this step, get ready to:
  • Create your Neon account without creating a project.
  • Prepare the runtime, code editor, and Git repository.
  • Launch MCP Inspector in your browser.
Create your Neon account

This step creates only your account. Project creation begins in the next step.

Your sign-in credentials stay with Neon. Use the account method you normally trust for development services.

  • Visit the Neon sign-up page in your browser.
  • Choose your preferred sign-up method.
  • Complete any account verification that Neon requests.
  • Confirm that the Neon Console loads after sign-in.

That is the first gate cleared: your Neon account is ready without any Neon resources created.

Install the local development tools

Node.js runs the server code on your machine. npm supplies the packages used by the server and Inspector.

MCP Inspector requires Node.js 22.19.0 or newer. Checking both commands confirms that the runtime and package manager are available.

  • Check your Node.js and npm versions by running these commands in a terminal:
node --version
npm --version

What do these checks prove?

  • The first command prints the installed Node.js version.
  • The second command confirms that npm was installed with Node.js.
  • A Node.js version of 22.19.0 or higher can run MCP Inspector.

✔️ I see Node 22.19.0 or higher

Your runtime meets MCP Inspector's requirement. Node.js and npm are ready for the project.

ⓧ I see an older version

  • Download the release marked LTS from the official Node.js download page.
  • Run the downloaded installer for your operating system.
  • Open a new terminal after the installer finishes.
  • Repeat the version checks above.

ⓧ Command not found

  • Download the release marked LTS from the official Node.js download page.
  • Run the downloaded installer for your operating system.
  • Open a new terminal after the installer finishes.
  • Repeat the version checks above.

Still seeing the old Node.js version?

  • Close every open terminal before checking the version again.
  • Restart your code editor if its terminal still reports the previous version.

Ask for help with your installation: Help me troubleshoot my Node.js and npm installation.

Visual Studio Code gives you a code editor with a terminal inside the project workspace. The shared interface keeps the remaining instructions consistent.

✔️ I already have Visual Studio Code

Your editor is ready. Continue below to create the project workspace.

ⓧ I need Visual Studio Code

  • Download Visual Studio Code from the official download page.
  • Run the installer for your operating system.
  • Accept the standard installation options.
  • Press Cmd+Space on macOS or the Windows key on Windows to open system search.
  • Type Visual Studio Code in the search field.
  • Press Enter to open Visual Studio Code.
  • Select File from the top menu.
  • Select Open Folder.
  • Create a folder named mcp-auth-server on your Desktop from the folder picker.
  • Select the mcp-auth-server folder.
  • Confirm with Open on macOS or Select Folder on Windows.
  • Select Yes, I trust the authors if Visual Studio Code asks whether you trust the empty folder.

You will see mcp-auth-server at the top of the Explorer sidebar. This confirms that Visual Studio Code is working inside the correct folder.

Git turns the empty folder into a repository that can track every server change. Check whether Git is available before initializing it.

  • Select View from the top menu.
  • Select Terminal.
  • Check whether Git is installed by running this command:
git --version

What does this check prove?

Git prints its installed version when the command is available. The version number confirms that Visual Studio Code can reach Git from its integrated terminal.

✔️ I see a Git version

Git is ready. Keep the integrated terminal open for the repository setup below.

ⓧ Command not found

  • Download Git from the official Git website.
  • Run the installer for your operating system.
  • Restart Visual Studio Code after the installer finishes.
  • Repeat the Git version check above.
  • Initialize the open mcp-auth-server folder as a Git repository by running this command:
git init

What does this command do?

Git adds its repository data to the open folder. Future commits can record each working version of your MCP server.

  • Verify that the repository responds by running this command:
git status

What does this status prove?

Git reports the state of the new repository. The empty status is expected because the server files are created in a later step.

Your editor now has an open workspace with active Git tracking. That gives every later server change a recoverable history.

Repository command failing?

  • Confirm that the integrated terminal starts inside the mcp-auth-server folder.
  • Restart Visual Studio Code if Git was installed while the editor was open.

Get help with the repository setup: Help me initialize this Git repository.

Launch MCP Inspector

MCP Inspector is the client you will use to connect to the protected server. Launching it now separates client setup problems from authorization problems later.

The first launch downloads the Inspector package. A short pause is expected while npm prepares it.

Before you run it, do you expect the browser client to open even though no MCP server exists yet?

  • Launch the MCP Inspector web client by running this command:
  • Approve the package download if npm prompts you during the launch.
npx @modelcontextprotocol/inspector

What does this command do?

The command runs MCP Inspector directly through npm without a global installation. It starts the local Inspector service and provides the browser session URL.

Your browser opens the MCP Inspector interface with no server connected. This proves that the client is ready before authentication enters the flow.

  • Confirm that your browser shows the MCP Inspector interface.
  • Keep the Inspector terminal process running while you complete the checkpoint below.

Inspector did not open?

  • Open the session URL printed in the terminal if the browser stays closed.
  • Confirm that the Node.js version check reports 22.19.0 or higher.
  • Stop any earlier Inspector process before launching the command again.

Ask for targeted help: Help me launch MCP Inspector.

Your prerequisites are ready. Next, you will create the Neon project that anchors your authorization server.

Create a Neon Project

Your MCP server needs an authorization service that can issue tokens for one database branch. A Neon project gives that authentication state a managed home.

In this step, you will create the project with Neon Auth enabled. You will make main the default branch before locating its OAuth provider settings.

In this step, get ready to:
  • Create a Neon project with Neon Auth enabled.
  • Rename the default root branch to main.
  • Open the OAuth provider settings for the main branch.
Create the Neon project

A Neon project is the top-level home for your branches. Turning on Neon Auth prepares the project to manage users and authorization settings.

Creating a cloud resource can feel like a billing step. The setup in this project fits the Neon Free plan.

  • Visit the Neon Console in your browser.
  • Sign in with your Neon account.
  • Click New Project.
  • Enter your MCP auth project name in the Project name field.
  • Select a Region close to your application where Neon Auth appears under Services.
  • Keep Postgres database turned on under Services.
  • Turn on Neon Auth under Services.
  • Click Create project.

Your first Neon project is ready. You will see its Overview page in the Console.

What did Neon create?

The project holds your database branches. Each branch keeps its own data and authentication configuration.

Neon Auth gives the project the managed authentication service that later issues tokens to your MCP client.

Project creation not working?

  • Return to the Region field if Neon Auth is missing under Services.
  • Choose a region where Neon Auth appears.
  • Check whether your account has reached its project limit if the creation request fails.

Help me troubleshoot Neon project creation. You can also ask the Neon Discord community.

Rename the default branch

Projects created through the Neon Console begin with a default root branch named production. Your project uses main as its default branch name.

  • Select Branches in the project sidebar.
  • Select the production branch from the table.
  • Open the three-dot menu on the branch overview page.
  • Select Rename.
  • Enter main as the new branch name.
  • Click Save.

You will see main carrying the DEFAULT tag. The root branch now matches the branch name used throughout this project.

Open the OAuth provider settings

Managed Better Auth stores its configuration per branch. Each branch has its own issuer for the tokens it creates.

  • Confirm that main remains selected in the Console branch selector.
  • Select Settings in the project sidebar.
  • Select Better Auth.
  • Select OAuth provider.

Before the final check, which branch do you expect this issuer to belong to?

  • Check the branch selector at the top of the Neon Console.

You will see main selected. The page displays issuer information and OAuth provider settings for that branch.

Can't see the OAuth provider settings?

  • Return to the project configuration if the Better Auth section is missing.
  • Confirm that Neon Auth is enabled for the project.
  • Confirm that main is selected before returning to OAuth provider.

Help me find the OAuth provider settings.

Your Neon project now has a default main branch with its OAuth provider settings ready. Next up, you will add the login page, consent page, and MCP server URLs.

Configure OAuth URLs

Your Neon project is ready. Now you will give its main-branch OAuth provider the destinations it needs.

Neon Managed Better Auth sends signed-out users to your login page. It sends signed-in users to your consent page.

The MCP server URL identifies the protected resource. Saving all three values gives the client a complete authorization route.

In this step, get ready to:
  • Prepare three publicly reachable OAuth destinations.
  • Register the user-facing page URLs.
  • Save the MCP server URL in the provider settings.
Prepare your public URLs

Public HTTPS lets Neon redirect users outside your local machine. Full page paths keep the sign-in screen separate from the consent screen.

  • Record your publicly reachable login page URL here: your public login page URL.
  • Record your publicly reachable consent page URL here: your public consent page URL.
  • Test your public login page URL in a private browser window.
  • Test your public consent page URL in a private browser window.

You should reach your hosted app at each address. A sign-in prompt or redirect is acceptable when the page requires a session.

  • Record your publicly reachable Express MCP server URL here: your public MCP server URL.

Why Can You Register the Server Early?

The MCP server URL identifies the resource that future access tokens are meant for. Neon can register this absolute URL before the server starts responding.

Register the user-facing pages

The page URLs tell the authorization server where each user-facing stage lives. Each value must point to its complete public path.

  • Switch back to the OAuth provider settings from earlier.
  • Confirm the current branch is main in the branch selector.
  • Turn on the Enable OAuth provider toggle.
  • Select the Login page URL field.
  • Paste your public login page URL into the field.
  • Select the Consent page URL field.
  • Paste your public consent page URL into the field.

You should see the full login page address in its field. You should also see the full consent page address in its field.

What Do These Pages Control?

The login page authenticates a user who does not have an active session.

The consent page shows what the client wants to access. The user can approve that request from this page.

Managed Better Auth carries the authorization request between these destinations. Your pages stay under your control.

Register the MCP server

The server URL links issued tokens to your Express MCP server. This keeps the authorization flow tied to the resource the client wants to use.

  • Scroll to the MCP server section.
  • Select the Server URL field.
  • Paste your public MCP server URL into the field.

You should see your complete Express MCP server address in the Server URL field.

Before you save, predict whether all three URLs will remain visible after the page refreshes.

  • Click Enable to save the provider configuration.
  • Refresh the Neon Console page.

You should see the saved login page URL in the provider settings. You should also see the saved consent page URL plus the saved Server URL.

Settings Not Staying Saved?

  • Check that every URL is complete.
  • Confirm that both page URLs use HTTPS.
  • Confirm that each page origin is trusted for the main branch.

Share the field that will not save through this troubleshooting prompt.

That configuration hurdle is behind you because Neon now has all three destinations. Next, you will build the Express MCP server that enforces this authorization flow.

Build the Protected MCP Server

Your Neon Managed Better Auth provider now holds the sign-in pages. It also knows the MCP server URL.

A registered URL cannot protect tool calls by itself. Your Express server must validate each bearer token before a request reaches a tool.

In this step, get ready to:
  • Prepare the Express and MCP runtime dependencies.
  • Build a protected MCP endpoint containing one tool.
  • Confirm that an unauthenticated request receives HTTP 401.
Install the server dependencies

The repository needs the MCP server SDK plus the OAuth middleware from Neon. The middleware connects your endpoint to the provider configuration on the main branch.

  • Switch back to your code editor.
  • Select your existing local Git repository as the workspace.
  • Open the editor's integrated terminal by pressing Ctrl+` on macOS or Windows.
  • Prepare the repository dependencies by running these commands:
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/sdk express
npm install @neon/oauth

What do these commands prepare?

  • The first command creates package.json for the server project.
  • The second command enables ES module imports.
  • The MCP SDK supplies the server and HTTP transport.
  • The @neon/oauth package supplies token validation plus MCP authorization metadata.
  • Check the terminal output for completed package installations.
  • Confirm that package.json now lists the installed packages under dependencies.

Dependency installation failing?

Make sure the terminal is inside your existing Git repository. Check that your network allows npm package downloads.

If npm reports a permissions problem, avoid running the installation with elevated privileges. Fix the ownership of your npm cache before trying again.

Help me troubleshoot these npm dependency installations.

Wire in OAuth and one tool

Token validation needs two exact URLs. The branch Auth URL pins the expected issuer. The registered MCP server URL pins the expected audience.

  • Return to the Neon Console from the earlier provider setup.
  • Copy the Auth URL for the default main branch into your main-branch Neon Auth URL.
  • Copy the saved MCP server URL into your registered MCP server URL.

Why pin both URLs?

The Auth URL identifies the provider on your main branch. A token from another branch carries a different issuer.

The MCP server URL identifies the intended recipient. This prevents a valid token for another resource from reaching your tool.

  • Return to your code editor.
  • Create server.mjs at the top level of your existing Git repository.
  • Add the server imports plus OAuth configuration by pasting this code into server.mjs:
import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { createMcpAuth } from '@neon/oauth/mcp';
import { expressMcp } from '@neon/oauth/mcp/express';

// Pin token validation to the main branch and registered MCP resource.
const auth = createMcpAuth({
  baseUrl: '[[NEON_AUTH_BASE_URL="your main-branch Neon Auth URL"]]',
  resource: '[[MCP_SERVER_URL="your registered MCP server URL"]]',
});

// Apply OAuth protection before any MCP request reaches a tool.
const app = express();
app.use(expressMcp(auth));
app.use(express.json());

// Use the host-provided port in deployment and port 3000 locally.
const port = process.env.PORT ?? 3000;
app.listen(port, () => {
  console.log(`Protected MCP server listening on port ${port}`);
});

What does this code do?

  • The baseUrl value pins validation to the main-branch issuer.
  • The resource value pins validation to the registered MCP server audience.
  • The expressMcp() middleware publishes the authorization metadata used by MCP clients.
  • The same middleware stops missing or invalid bearer tokens before they reach a route.
  • Save server.mjs.
  • Start the initial Express process by running this command:
node server.mjs

What should you see?

The command starts the process on the host-provided port or local port 3000. You should see Protected MCP server listening on port 3000 when you run it locally.

  • Stop the initial process by pressing Ctrl+C.
  • In server.mjs, find the line that begins with const port.
  • Insert the protected MCP route directly above that line by pasting this code:
// Handle each MCP request with a fresh stateless server and transport.
app.post('/mcp', async (req, res) => {
  const server = new McpServer({
    name: 'protected-tools',
    version: '1.0.0',
  });

  // Expose one tool that only authenticated requests can reach.
  server.registerTool(
    'private-message',
    {
      description: 'Return a message for an authenticated user',
      inputSchema: {},
    },
    async () => ({
      content: [{ type: 'text', text: 'You reached a protected MCP tool.' }],
    }),
  );

  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });

  // Close per-request resources when the response ends.
  res.on('close', () => {
    transport.close();
    server.close();
  });

  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

How does the protected route work?

  • The /mcp route creates a fresh stateless MCP server for each request.
  • The private-message tool returns one text result to an authenticated client.
  • The HTTP transport passes MCP requests to the registered tool.
  • The earlier OAuth middleware decides whether a request can enter this route.
  • Save server.mjs.
  • Start the completed MCP server by running this command:
node server.mjs

What is running now?

The process now serves the protected /mcp endpoint. The private-message tool sits behind the OAuth middleware.

Server not starting?

Check that both URL placeholders contain complete URLs. Make sure the MCP server URL exactly matches the value saved in the provider settings.

If an import fails, confirm that the terminal is running from the repository containing package.json plus node_modules.

Help me fix my protected MCP server startup error.

✔️ Awesome, I've got everything!

Your file now contains the OAuth configuration, one protected MCP route, and one tool. Keep the server process running for the unauthenticated request test.

ⓧ I'd like to double check the full code

Compare your complete server.mjs file with this reference:

import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { createMcpAuth } from '@neon/oauth/mcp';
import { expressMcp } from '@neon/oauth/mcp/express';

// Pin token validation to the main branch and registered MCP resource.
const auth = createMcpAuth({
  baseUrl: '[[NEON_AUTH_BASE_URL="your main-branch Neon Auth URL"]]',
  resource: '[[MCP_SERVER_URL="your registered MCP server URL"]]',
});

// Apply OAuth protection before any MCP request reaches a tool.
const app = express();
app.use(expressMcp(auth));
app.use(express.json());

// Handle each MCP request with a fresh stateless server and transport.
app.post('/mcp', async (req, res) => {
  const server = new McpServer({
    name: 'protected-tools',
    version: '1.0.0',
  });

  // Expose one tool that only authenticated requests can reach.
  server.registerTool(
    'private-message',
    {
      description: 'Return a message for an authenticated user',
      inputSchema: {},
    },
    async () => ({
      content: [{ type: 'text', text: 'You reached a protected MCP tool.' }],
    }),
  );

  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });

  // Close per-request resources when the response ends.
  res.on('close', () => {
    transport.close();
    server.close();
  });

  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

// Use the host-provided port in deployment and port 3000 locally.
const port = process.env.PORT ?? 3000;
app.listen(port, () => {
  console.log(`Protected MCP server listening on port ${port}`);
});

What should match?

Your imports, URL values, middleware order, tool name, route, and port handling should match this file. The OAuth middleware must appear before the /mcp route.

Run the protected endpoint

The final check must reach the exact MCP server URL saved in Neon. Choose the path that matches how that URL reaches your Express process.

My URL reaches my local process

  • Keep the terminal running server.mjs.
  • Confirm that your existing public forwarding setup sends the registered MCP server URL to local port 3000.
  • Open a second terminal for the unauthenticated request.

My URL uses a deployment host

  • Stop the local process by pressing Ctrl+C.
  • Return to the hosting service associated with your registered MCP server URL.
  • Set the service's start command to the following value:
node server.mjs

What does the host run?

The hosting service starts the same Express process you tested locally. The server reads the platform's assigned port from process.env.PORT.

  • Deploy the current repository revision.
  • Wait for the deployment logs to show the protected MCP server listening on the assigned port.
  • Open a local terminal for the unauthenticated request.

Before you send the request, do you expect the tool list to be returned without a bearer token?

  • Call the registered MCP server URL without an access token by running this command:
curl -i -X POST [[MCP_SERVER_URL="your registered MCP server URL"]] -H "Content-Type: application/json" -H "Accept: application/json, text/event-stream" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

What does the response prove?

You should see HTTP status 401 before any tool list is returned. The request reached your server but did not pass token validation.

The middleware also points an MCP client toward protected-resource metadata. That metadata starts the sign-in flow in the next step.

Not seeing HTTP 401?

A connection failure means the registered URL is not reaching the running Express process. Check the local forwarding process or the latest deployment status.

An HTTP 404 means the saved URL may not end at the /mcp route. Compare the requested URL with the provider setting.

Help me diagnose my MCP endpoint response.

That is the protection boundary working. Your MCP endpoint now refuses anonymous tool access at the registered server URL.

Next up, you will connect an MCP client and follow the sign-in flow through your login page plus consent page.

Sign In Through Your MCP Client

Your MCP server already rejects requests without a trusted token. This step proves a client can pass through the permission flow.

Now you will connect an MCP client to the registered endpoint. Neon Managed Better Auth uses OAuth to send you through the saved login page.

The consent page asks you to approve access. A successful approval gives the client a main-branch access token.

In this step, get ready to:
  • Connect your MCP client to the registered server URL.
  • Complete the configured login flow.
  • Approve access on the configured consent page.
Connect the client to your server

The protected server URL becomes the client's starting point for authorization. The client uses that address to reach the login flow you configured earlier.

  • Keep the server from earlier available at its registered URL.
  • Switch back to the MCP client or MCP Inspector from earlier.
  • Choose the client control for adding a server connection.
  • Paste the MCP server URL saved in Neon OAuth provider settings into the server address field.

You'll see the registered MCP server URL in the connection settings. This confirms the client is targeting the protected endpoint.

  • Start the connection using the client's connection control.

Clients hand off authorization through a browser window or a displayed authorization link.

✔️ My browser opened

  • Continue in the browser window opened by your client.

ⓧ I see an authorization link

  • Select the authorization link shown by your client.

You'll see your configured login page in the browser. This proves the client reached the main-branch authorization flow.

Login page missing?

  • Confirm the client is using the exact MCP server URL saved in the OAuth provider settings.
  • Confirm the server remains reachable at that registered URL.
  • Use the authorization link shown by the client if no browser window opened.

Help me diagnose why my MCP client cannot reach the configured login page.

Authorize the client

Sign-in pages handle credentials, so take ten seconds to check the address before you type anything. The address should match the login page URL you registered earlier.

  • Compare the browser address with the login page URL you registered earlier.
  • Complete the sign-in method offered by your login page.

You'll see your configured consent page after sign-in succeeds. The page asks whether this client can access the MCP server.

What do login and consent prove?

  • Login confirms your identity to Neon Managed Better Auth.
  • Consent records your decision to let this client access the protected MCP server.
  • The access token carries the approved authorization back to the client.
  • Review the client details shown on the consent page.
  • Review the requested access shown on the consent page.
  • Approve the request using the page's approval control.

That's the key handoff complete. Your client now holds a valid access token issued by the main-branch OAuth provider.

Consent flow not completing?

  • Confirm the login page reported a successful sign-in.
  • Confirm the consent page belongs to the URL registered in Neon.
  • Confirm the MCP server URL still matches the saved main-branch provider entry.

Help me troubleshoot an MCP OAuth flow that reaches login but does not complete consent.

Verify the authenticated connection

The client connection state is the final proof that the browser handoff finished. It also confirms the client retained the access token.

Before you check the connection status, do you expect the client to request authorization again or keep the server connection active?

  • Switch back to the MCP client.
  • Select the server connection you just authorized.

You'll see an active server connection without another sign-in request. This confirms the client holds a valid token from the main-branch OAuth provider.

Your MCP client is signed in with permission to reach the protected server. Next up, you'll use that authenticated connection to call the tool.

Call the Protected Tool

Your MCP client has completed the OAuth flow. It now holds a valid access token from your main-branch provider.

A successful sign-in matters when it unlocks protected work. In this step, you will call the protected tool from your authorized client.

In this step, get ready to:
  • Call the server's protected MCP tool from your authorized client.
  • Verify that the protected tool returns its result without another sign-in.
  • Compare the authorized result with the earlier HTTP 401 response.
Invoke the protected tool

The client attaches its access token to the request. The @neon/oauth middleware checks that token before Express lets the request reach the tool.

  • Switch back to the MCP client from earlier.
  • Return to the existing MCP server connection in the client.
  • Check the connection status for the registered MCP server.

You should see that the server remains connected. Its only protected tool should be available to use.

  • Predict whether the client can reach the tool without repeating sign-in.
  • Ask the client to call the only tool available from the connected server.
  • Approve the tool request if your client asks for confirmation.

Your client should show the protected tool's result. The login page should stay closed.

The consent page should stay closed too.

You have crossed the authorization boundary. Your signed-in client can now use a tool that anonymous requests cannot reach.

Tool result missing?

  • Reconnect the existing server connection if the protected tool is missing.
  • Repeat the OAuth flow if the client reports expired authorization.
  • Confirm that the Express MCP server is still running at the registered URL if the connection is unavailable.

Help me troubleshoot this protected MCP tool call.

Compare both authorization outcomes

A request with no token stops at the authorization boundary. The signed-in request reaches the protected tool.

  • Predict which of your two requests should reach the protected tool.
  • Return to the earlier no-token test result.
  • Confirm that it shows HTTP status 401.
  • Switch back to the MCP client from earlier.
  • Confirm that the protected tool result remains visible.

The no-token request is rejected with HTTP status 401. The authorized client receives the tool result.

That is the security boundary working from request to result. The server blocks anonymous access while your signed-in client can use the tool.

Your protected tool now works for signed-in users. Next, you will create a preview branch to test how branch-specific tokens behave.

Test Branch Token Isolation

Your authorized MCP client can call the protected tool with a main-branch access token. Now you need to prove the unchanged Express server keeps preview credentials out.

Each Neon branch has its own OAuth issuer through Managed Better Auth. In this step, you will create a preview branch. You will test its token against the server pinned to main.

In this step, get ready to:
  • Create a temporary preview branch from the main branch.
  • Issue a token through the preview branch authorization server.
  • Prove branch isolation with preview and main tokens.
Create a preview branch from main

A preview branch gives you a separate authentication environment based on the current main branch. This creates a realistic credential for the isolation test.

  • Return to your Neon project in the Neon Console.
  • Select Branches in the sidebar.
  • Click New branch.
  • Choose main as the Parent branch.
  • Enter oauth-isolation-preview in the Branch name field.
  • Select Current data.
  • Clear the Automatically delete branch after checkbox.
  • Click Create.

Good progress. You will see the branch overview for oauth-isolation-preview. Your temporary authentication environment is ready.

What does the branch copy?

The child branch starts with a copy of the main branch's users. It also copies sessions plus provider configuration.

The preview branch receives a unique Auth URL. That URL identifies its own OAuth issuer.

Issue a preview token and test it

The temporary token must come from the preview branch's Auth URL. A separate client connection preserves the authorized main connection as your control.

  • Select Settings while oauth-isolation-preview is the active branch.
  • Select Better Auth.
  • Select Project Info.
  • Record the displayed Auth URL here: your preview branch Auth URL.

This branch-specific URL tells the client which authorization server should issue the temporary token.

  • Return to the MCP client from earlier.
  • Add a temporary MCP connection that uses the registered MCP server URL from earlier.
  • Set the temporary connection's authorization server to your preview branch Auth URL.
  • Start the sign-in flow for the temporary connection.
  • Sign in through the configured login page.
  • Approve the requested access on the configured consent page.

Once the consent page closes, the temporary connection is authorized through the preview branch.

Preview sign-in not opening?

Check that the recorded Auth URL came from Project Info while the preview branch was active. A main-branch URL repeats the authorization flow from earlier.

If your client follows server metadata automatically, use its custom authorization server setting for the temporary connection.

Ask for help with the client-specific setting: Help me point my temporary MCP connection at the preview branch Auth URL without changing my server URL.

Before the tool call, picture what the unchanged main-branch verifier should do with the preview token.

  • Call the protected tool from the temporary preview connection.

You will see the request rejected. If your client displays the response status, you will see HTTP 401.

Why was the token rejected?

Neon branches share signing keys. A preview token can therefore carry a valid signature.

The @neon/oauth verifier also checks the token issuer plus its audience. Your server remains pinned to the main issuer. The preview issuer therefore fails validation.

That rejection is the win. Preview credentials cannot cross into the MCP server pinned to main.

Confirm the main token still works

A negative security test needs a control result. The original main-branch connection proves the protected tool still works for the correct issuer.

  • Remove the temporary preview connection from your MCP client.
  • Switch back to the authorized main-branch connection from earlier.

Before the control call, picture the result you expect from the main token that worked earlier.

  • Call the same protected tool from the main-branch connection.

You will receive the protected tool's result again. This confirms that the server still accepts the valid main-branch token.

Main tool call failing?

  • Confirm that you selected the original main-branch connection.
  • Repeat the main-branch login flow if its access token has expired.
  • Confirm that the Express server remains running at the registered MCP server URL.

Get help without sharing credentials: Help me diagnose why my main-branch MCP connection stopped calling the protected tool after my preview-branch isolation test.

Strong finish. Your main-branch client still reaches the protected tool.

Your complete authorization test now covers three outcomes: unauthenticated calls receive HTTP 401, main-branch tokens succeed, plus preview-branch tokens are rejected.

Secret mission

Create an Authorization Evidence Pack

Turn your three authorization outcomes into a security evidence pack another engineer can review. You will capture redacted proof, map each credential state to its result, and define the branch boundary future changes must preserve.

Clean Up Your Resources

Clean Up Your Resources

The Neon Managed Better Auth setup can stay within the researched Free plan allowance. A deployed server may follow your hosting provider's pricing, so decide whether to keep, pause, or delete each resource.

Resources you used:

  • Neon project containing the default main branch plus the saved OAuth provider settings.
  • Temporary Neon preview branch used for the branch-isolation test.
  • Local Git repository containing the Express MCP server source.
  • Express MCP server process or deployment available at the registered server URL.
  • Authorized MCP client connection holding a valid main-branch access token.

Keep everything running

No action needed. Choose this if you are still testing the authorization flow or want to reuse the server for private MCP tools.

  • Keep the Neon project available in your account.
  • Keep the temporary preview branch available for more branch-isolation tests.
  • Keep the Express MCP server available at its registered URL.
  • Keep the local Git repository as a starting point for future private MCP tools.
  • Keep the authorized MCP client connection for future tool calls.

The Neon resources remain within the researched Free plan allowance for this project. Your hosting provider determines any separate deployment costs.

Pause - I'll come back to this later

Shut down the running server to free up local resources or hosting capacity. Your main-branch configuration remains ready for your return.

Pause the server:

  • Switch back to the terminal running the Express MCP server if you started it locally.
  • Press Ctrl+C to stop the local server process.
  • Return to your deployment service if the server is deployed.
  • Use the deployment service's documented pause control.
  • Confirm that the MCP client can no longer reach the protected tool.

Clean up the temporary branch:

  • Return to the Neon Console project from earlier.
  • Open the branch list for that Neon project.
  • Select the temporary preview branch used for the isolation test.
  • Use the branch deletion control.
  • Confirm that the temporary preview branch no longer appears in the branch list.

That removes the temporary issuer used for the isolation test. Your main Neon project plus the local Git repository remain ready for future work.

Delete - I don't want to use this again

Remove all project resources to start fresh. This permanently removes the authentication setup plus the server source.

Permanent deletion is a significant step. The sequence below removes one layer at a time so you can confirm each result.

Delete the temporary preview branch:

  • Return to the Neon Console project from earlier.
  • Open the branch list for that Neon project.
  • Select the temporary preview branch used for the isolation test.
  • Use the branch deletion control.
  • Confirm that the temporary preview branch no longer appears in the branch list.

Remove the MCP server:

  • Remove the registered server connection from the MCP client's connection settings.
  • Switch back to the terminal running the Express MCP server if you started it locally.
  • Press Ctrl+C to stop the local server process.
  • Return to your deployment service if the server is deployed.
  • Use the deployment service's documented resource deletion path.
  • Confirm that the registered MCP server URL no longer responds.

Delete the Neon project:

  • Open the project settings area for the same Neon project.
  • Choose the project deletion control.
  • Complete the deletion confirmation.
  • Confirm that the Neon project no longer appears in your project list.

Deleting the Neon project removes its main branch plus the saved OAuth provider settings.

Delete the local repository:

  • Locate the local Git repository from earlier with your operating system's file browser.
  • Move the repository folder to your operating system's deleted-items location.
  • Empty the deleted-items location to remove the source permanently.
  • Confirm that the repository folder no longer appears in its previous location.

Your cleanup is complete. The temporary branch plus the authorization server resources are gone.

Nice Work!

Nice Work!

You did it! Your Express MCP server now requires users to sign in before an AI client can access its protected tool.

You now know how to:

  • Configure Neon Managed Better Auth as an OAuth provider for a user-controlled sign-in flow.
  • Protect an MCP server so unauthenticated requests receive HTTP 401. Use a valid main-branch access token to call its protected tool.
  • Prove branch isolation by watching the main-branch-pinned server reject a preview-branch token.
  • Secret Mission: Apply the authorization checks from this project in an optional challenge that extends the protected MCP workflow.

Ready to quiz yourself?