oauth-mcp-server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@oauth-mcp-server list the available tools"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
oauth-mcp-server
A production-grade Model Context Protocol (MCP) resource server with OAuth 2.1 authorization. Written in TypeScript with the official @modelcontextprotocol/sdk.
It is provider-agnostic — swap between Auth0, Keycloak, Google, Entra ID, Okta, or any OAuth 2.1-compliant authorization server by changing one configuration file.
Compatible with both MCP specification versions:
2026-07-28 (primary: per-request
_meta,server/discover, stateless auth)2025-11-25 (backward compat:
initializehandshake)
Quick Start
Requires Node.js >= 18. All commands below are PowerShell (Windows). For macOS/Linux, replace
$env:VAR = "value"withVAR=value.
# 1. Install dependencies
npm install
# 2. Build the TypeScript
npm run build
# 3. Start the server on port 6000 (dev mode — no OAuth required)
$env:PORT = "6000"; $env:MCP_NO_AUTH = "true"; npm run start:httpVerify it's running:
# Health check
curl http://localhost:6000/health
# Protected Resource Metadata (RFC 9728)
curl http://localhost:6000/.well-known/oauth-protected-resourceExpected output:
{"status":"ok","server":"oauth-mcp-server v1.0.0","auth":"disabled (dev mode)","sessions":0}Running on a Custom Port
The server reads the PORT environment variable. The config file uses port 6000 by default. To use a different port, set both:
# Pick any free port
$env:PORT = "8080"; npm run start:httpIf you change the port, also update these fields in your config file to match (config/default.json):
{
"baseUrl": "http://localhost:8080",
"authorization": {
"validation": {
"jwks": {
"uri": "http://localhost:8080/.well-known/jwks.json",
"issuer": "http://localhost:8080"
}
},
"resourceMetadata": {
"authorizationServers": ["http://localhost:8080"],
"resource": "http://localhost:8080"
}
}
}The port appears in 5 places in the config — they must all match the port you actually run on.
Related MCP server: MCPAuth
Setup Guide: Local Development
For local development, start with OAuth disabled. This unlocks all capabilities without needing any tokens — perfect for testing the server itself.
# Build and start in dev mode
npm run build
$env:PORT = "6000"; $env:MCP_NO_AUTH = "true"; npm run start:httpVariable | What it does |
| Port to listen on (defaults to 6000) |
| Set to |
| Path to a config JSON file (defaults to |
| Bind address (defaults to |
Testing with curl (no auth)
# Discover server capabilities
$body = '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientCapabilities":{}}}}'
curl -X POST http://localhost:6000/mcp -H "Content-Type: application/json" -H "MCP-Protocol-Version: 2026-07-28" -d $body
# List tools (all 18 available since auth is off)
$body = '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
curl -X POST http://localhost:6000/mcp -H "Content-Type: application/json" -H "MCP-Protocol-Version: 2026-07-28" -d $body
# Call the echo tool
$body = '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"echo","arguments":{"text":"hello world"}}}'
curl -X POST http://localhost:6000/mcp -H "Content-Type: application/json" -H "MCP-Protocol-Version: 2026-07-28" -d $bodyLocal dev with live reload
# stdio transport (no build step, auto-reloads on code changes)
npm run dev:stdio
# HTTP transport, no OAuth, auto-reloads
$env:MCP_NO_AUTH = "true"; npm run dev:httpSetup Guide: Enabling OAuth (Local Testing with a Real Provider)
When you're ready to test the full OAuth flow, point the server at a real authorization server. Here's the step-by-step for Auth0 (the same pattern works for Keycloak, Okta, etc.).
Step 1: Create an API in Auth0
Go to Auth0 Dashboard → Applications → APIs → Create API
Set Name:
MCP ServerSet Identifier:
http://localhost:6000(this becomes youraudience)Set Signing Algorithm:
RS256Click Create
Step 2: Add scopes to the API
In your Auth0 API settings, add these scopes:
mcp:read
mcp:write
mcp:adminStep 3: Create a Machine-to-Machine client
Go to Applications → Create Application
Name it
MCP ClientChoose Machine to Machine
Select the
MCP ServerAPIGrant it
mcp:readandmcp:writescopesNote the Client ID and Client Secret
Step 4: Update the server config
Copy the Auth0 example and fill in your details:
Copy-Item config\auth0.example.json config\production.jsonEdit config/production.json — the three fields you must change:
{
"baseUrl": "http://localhost:6000",
"authorization": {
"validation": {
"jwks": {
"uri": "https://YOUR_DOMAIN.us.auth0.com/.well-known/jwks.json",
"issuer": "https://YOUR_DOMAIN.us.auth0.com/",
"audience": "http://localhost:6000"
}
},
"resourceMetadata": {
"authorizationServers": ["https://YOUR_DOMAIN.us.auth0.com"],
"resource": "http://localhost:6000"
}
}
}Field | Where to find it |
| Auth0 Dashboard → your tenant domain (e.g. |
|
|
|
|
| The API Identifier you set in Step 1 |
Step 5: Start the server with OAuth enabled
# Omit MCP_NO_AUTH — OAuth is now active
$env:PORT = "6000"; $env:MCP_CONFIG_PATH = "config/production.json"; npm run start:httpStep 6: Get a token and test
# Get a token from Auth0 (Client Credentials flow)
$tokenResponse = curl -X POST https://YOUR_DOMAIN.us.auth0.com/oauth/token `
-H "Content-Type: application/json" `
-d '{"client_id":"YOUR_CLIENT_ID","client_secret":"YOUR_CLIENT_SECRET","audience":"http://localhost:6000","grant_type":"client_credentials","scope":"mcp:read"}'
$token = ($tokenResponse | ConvertFrom-Json).access_token
# Now call the server with the token
$body = '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
curl -X POST http://localhost:6000/mcp `
-H "Content-Type: application/json" `
-H "MCP-Protocol-Version: 2026-07-28" `
-H "Authorization: Bearer $token" `
-d $bodyWhat happens with no token (OAuth enabled)
# Without a token, the server returns 401
curl -X POST http://localhost:6000/mcp `
-H "Content-Type: application/json" `
-H "MCP-Protocol-Version: 2026-07-28" `
-d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{}}'
# Response:
# HTTP 401 Unauthorized
# WWW-Authenticate: Bearer resource_metadata="http://localhost:6000/.well-known/oauth-protected-resource", scope="mcp:read"Provider-specific notes
Provider | JWKS URI pattern | Issuer pattern | Notes |
Auth0 |
|
| Trailing slash on issuer |
Keycloak |
|
| No trailing slash |
Okta |
|
| Use the authorization server ID |
Entra ID |
|
| Use tenant ID, not domain |
Setup Guide: Production Deployment
Configuration changes for production
The only differences between local and production config are the URLs — everything else stays the same:
{
"baseUrl": "https://mcp.your-company.com",
"authorization": {
"validation": {
"jwks": {
"uri": "https://YOUR_DOMAIN.us.auth0.com/.well-known/jwks.json",
"issuer": "https://YOUR_DOMAIN.us.auth0.com/",
"audience": "https://mcp.your-company.com"
}
},
"resourceMetadata": {
"authorizationServers": ["https://YOUR_DOMAIN.us.auth0.com"],
"resource": "https://mcp.your-company.com"
}
}
}Config field | Local value | Production value |
|
|
|
| Your provider's JWKS URL | Same provider URL (unchanged) |
| Your provider's issuer | Same (unchanged) |
|
|
|
|
|
|
| Your provider | Same (unchanged) |
Key insight: The
authorizationServersand JWKS/issuer fields point to your OAuth provider (which doesn't change between local and prod). Only thebaseUrl,audience, andresourcefields reflect where this server is running.
Deploying to Google Cloud Run
npm run buildCloud Run sets PORT (usually 8080) and K_SERVICE automatically. The server detects K_SERVICE and binds to 0.0.0.0. Set your config path as an env var in Cloud Run:
MCP_CONFIG_PATH=config/production.jsonA Dockerfile is included for container-based deployments. After deployment:
MCP endpoint:
https://<your-cloud-run-url>/mcpHealth:
https://<your-cloud-run-url>/healthMetadata:
https://<your-cloud-run-url>/.well-known/oauth-protected-resource
Deploying to Vercel
api/
├── health.ts # Health check (no auth)
└── mcp.ts # Stateless Streamable HTTP handler with OAuthDeploy via Vercel Git integration — no build step or framework preset needed. Set these env vars in Vercel:
MCP_CONFIG_PATH=config/production.json
MCP_CORS_ORIGIN=https://your-client.example.comAfter deployment:
MCP endpoint:
https://<project>.vercel.app/api/mcpHealth:
https://<project>.vercel.app/api/healthMetadata:
https://<project>.vercel.app/.well-known/oauth-protected-resource
How OAuth Works on This Server
This server acts as an OAuth 2.1 Resource Server. It never issues tokens — it only validates tokens issued by an authorization server.
The Flow
MCP Client This Server Auth Server
│ │ │
│ POST /mcp (no token) │ │
│ ──────────────────────────────────►│ │
│ │ │
│ HTTP 401 │ │
│ WWW-Authenticate: Bearer │ │
│ resource_metadata="...", │ │
│ scope="mcp:read" │ │
│ ◄──────────────────────────────────│ │
│ │ │
│ [Client discovers auth server, │ │
│ gets a Bearer token via OAuth] │ │
│ ───────────────────────────────────────────────────────────────►│
│ ◄───────────────────────────────────────────────────────────────│
│ │ │
│ POST /mcp │ │
│ Authorization: Bearer <token> │ │
│ MCP-Protocol-Version: 2026-07-28 │ │
│ ──────────────────────────────────►│ │
│ │ Validate JWT signature │
│ │ Check exp, iss, aud │
│ │ Resolve scopes→permissions│
│ │ Filter capabilities │
│ HTTP 200 + MCP response │ │
│ ◄──────────────────────────────────│ │Token Validation Modes
Mode | How it works | When to use |
| Fetches JWKS from your provider, verifies signature locally. Fast, no network call per request after JWKS is cached. | Auth0, Okta, Keycloak — most providers |
| Sends the token to the provider's introspection endpoint on every request. Works with opaque tokens. | When tokens are not JWTs, or you need real-time revocation |
| Tries JWT validation first; falls back to introspection if JWT fails. | When your provider issues both JWT and opaque tokens |
Scope Model
Different OAuth scopes unlock different subsets of capabilities. The default config defines:
Scope | What it unlocks |
(no token) |
|
| Public tools + search documents, query analytics, list projects/files, read resources |
| Everything in read + create/update documents, create/update projects, upload files |
| Everything — including |
| Just |
| Just |
| File system tools |
| Project management tools |
Scopes are additive and resolved in the order listed — if a single token has both mcp:read and mcp:files:write, the user gets the union of both permission sets.
Configuration Reference
Environment Variables
Variable | Default | Description |
|
| HTTP port to listen on |
|
| Bind address ( |
|
| Path to the OAuth config JSON file |
| (not set) | Set to |
| (not set) | A static JWT to use for local testing (bypasses real OAuth flow) |
|
| Max characters logged per request/response body |
|
| CORS origin for Vercel/public deployments |
Config File Structure
The config file (config/default.json or your own) controls everything about the OAuth integration:
{
"name": "oauth-mcp-server",
"version": "1.0.0",
"baseUrl": "http://localhost:6000",
"authorization": {
"validation": {
"mode": "jwt",
"jwks": {
"uri": "https://YOUR_DOMAIN/.well-known/jwks.json",
"cacheTtlSeconds": 3600,
"allowedAlgorithms": ["RS256"],
"issuer": "https://YOUR_DOMAIN/",
"audience": "http://localhost:6000"
}
},
"resourceMetadata": {
"authorizationServers": ["https://YOUR_DOMAIN"],
"scopesSupported": ["mcp:read", "mcp:write", "mcp:admin"],
"bearerMethodsSupported": ["header"],
"resourceSupported": true,
"resource": "http://localhost:6000"
},
"scopeMapping": {
"scopes": {
"mcp:read": {
"tools": ["echo", "calculate", "current_time", "..."],
"resources": ["config://app", "docs://*", "..."],
"prompts": ["summarize", "code_review", "..."]
}
},
"defaultPermissions": {
"tools": ["echo", "current_time"],
"resources": ["config://app", "docs://about"],
"prompts": []
}
}
},
"capabilities": {
"tools": { "listChanged": true },
"resources": { "subscribe": true, "listChanged": true },
"prompts": { "listChanged": true }
},
"instructions": "OAuth-protected MCP resource server. ..."
}Tool/resource/prompt names in scopeMapping support glob patterns — "*" matches everything, "docs://*" matches all document resources.
Provider Presets
File | Provider | Validation |
| Local dev | JWKS (dev keys) |
| Auth0 | JWKS RS256 |
| Keycloak | JWKS RS256, ES256 |
HTTP API Reference
Method | Path | Auth Required | Purpose |
|
| Yes (Bearer token) | JSON-RPC requests |
|
| Session ID | Server-Sent Events stream |
|
| Session ID | Terminate a session |
|
| No | Health check |
|
| No | RFC 9728 metadata |
|
| No | Path-specific metadata |
Capabilities Inventory
Tools (18)
Category | Tool | Description | Scope |
Utility |
| Text echo — connectivity check | Public |
| add, subtract, multiply, divide, power, sqrt | Public | |
| Server time in any IANA timezone (structured output) | Public | |
| Random integer in [min, max] range | Public | |
| count, sum, min, max, mean, median, stddev | Public | |
Data Ops |
| Full-text search across 15+ articles |
|
| DAU, revenue, churn, regional data, feature usage |
| |
| Create a new KB article |
| |
| Update an existing KB article |
| |
| List/filter 12+ mock projects |
| |
| Create a new project |
| |
| Update project status/progress |
| |
File System |
| Browse mock directory tree |
|
| Read mock file contents |
| |
| Create/overwrite a mock file |
| |
Admin |
| Uptime, requests, memory, auth info |
|
| Hot-reload OAuth config from disk |
| |
| List all tools, resources, prompts |
|
Resources (10)
URI | Description | Scope |
| Server config and feature flags (JSON) | Public |
| About this server (Markdown) | Public |
| KB article by ID |
|
| 10 most recent articles |
|
| Current analytics snapshot |
|
| Regional user/revenue data |
|
| Project details by ID (listed) |
|
| Mock file content by path |
|
| Current user profile from token |
|
| Server health and auth info |
|
Prompts (5)
Prompt | Arguments | Description | Scope |
|
| Summarize text (bullet/paragraph/tweet/executive) |
|
|
| Structured code review with senior engineer persona |
|
|
| Guided brainstorming session |
|
|
| Data exploration with tool guidance |
|
|
| Structured refactoring with architect persona |
|
Using with Claude Desktop (stdio)
Add to claude_desktop_config.json:
{
"mcpServers": {
"oauth-mcp-server": {
"command": "node",
"args": ["C:\\Users\\Shylendra\\git\\oauth-mcp-server\\dist\\stdio.js"]
}
}
}The stdio transport does not use OAuth per the MCP spec — it runs in full-access mode. For OAuth, use the HTTP transport.
Testing
npm run test:smoke # stdio transport — validates all 18 tools, 10 resources, 5 prompts
npm run test:vercel # Vercel serverless handlerMCP Inspector
npm run inspect # stdio transportOr for HTTP: start the server, then open the Inspector and connect with Transport type: Streamable HTTP, URL: http://localhost:6000/mcp.
Project Layout
src/
├── server.ts # McpServer factory (auth-aware)
├── stdio.ts # stdio transport entry point
├── http.ts # Streamable HTTP entry point (OAuth)
├── banner.ts # Startup banner
├── logging.ts # Structured JSON logging
├── auth/
│ ├── index.ts # Public API surface
│ ├── types.ts # ValidatedToken, AuthContext, etc.
│ ├── token-validator.ts # JWT/JWKS + introspection
│ ├── scope-resolver.ts # OAuth scopes → MCP permissions
│ ├── discovery.ts # RFC 9728 metadata + WWW-Authenticate
│ └── errors.ts # 401/403/400 error responses
├── config/
│ ├── types.ts # Config type definitions
│ └── loader.ts # Config loading + Zod validation
├── capabilities/
│ ├── tools/ # 18 tools in 4 categories
│ ├── resources/index.ts # 10 resources
│ └── prompts/index.ts # 5 prompts
└── data/
├── users.ts # 8 mock users
├── documents.ts # 15+ knowledge base articles
├── projects.ts # 12+ mock projects
├── analytics.ts # 30-day metrics, regional data
└── files.ts # Mock directory tree
config/
├── default.json # Dev config (port 6000)
├── auth0.example.json # Auth0 preset
└── keycloak.example.json # Keycloak preset
api/
├── health.ts # Vercel health endpoint
└── mcp.ts # Vercel serverless OAuth handler
scripts/
├── smoke.mjs # stdio smoke test
├── smoke-http.mjs # HTTP smoke test
├── smoke-vercel.ts # Vercel smoke test
└── gen-jwks.mjs # Dev JWKS generatorStandards Compliance
MCP 2026-07-28 — Full spec:
server/discover, per-request_meta, stateless authMCP 2025-11-25 — Backward compatible
initializehandshakeOAuth 2.1 (draft-ietf-oauth-v2-1-13) — Resource server role
RFC 6750 — Bearer Token Usage
RFC 8414 — OAuth 2.0 Authorization Server Metadata
RFC 8707 — Resource Indicators for OAuth 2.0
RFC 9207 — Authorization Server Issuer Identification
RFC 9728 — OAuth 2.0 Protected Resource Metadata
License
MIT
Available Tools
18 toolscalculateCalculatorA
Performs a basic arithmetic operation on two numbers. Supports add, subtract, multiply, divide, power, and sqrt.
Inputs: a (required), operation (required), b.
create_documentCreate DocumentB
Creates a new knowledge base article in the mock document store.
Inputs: title (required), content (required), category (required), tags.
create_projectCreate ProjectB
Creates a new project in the mock project management system.
Inputs: name (required), description (required), tags, deadline.
current_timeCurrent TimeA
Returns the current server time in the requested IANA timezone (e.g., 'America/New_York', 'Asia/Tokyo'). Defaults to UTC.
Inputs: timezone. Returns: iso, unix, timezone, dayOfWeek, +1 more
echoEchoA
Echoes back the text you send. Useful as a connectivity check to verify the server is reachable.
Inputs: text (required).
get_server_statsServer StatisticsB
Returns server health metrics including uptime, request count, memory usage, and the OAuth provider configuration summary.
Inputs: includeMemory. Returns: uptime, platform, nodeVersion, authProvider, +2 more
list_filesList FilesA
Lists files and directories in a mock file system. Use '/' to see the root, or a directory path like '/project/src' for subdirectories.
Inputs: path.
list_projectsList ProjectsC
Lists mock projects with optional filters for status, owner, and tag.
Inputs: tag, status.
list_server_capabilitiesList Server CapabilitiesA
Lists all available tools, resources, and prompts that this server exposes, grouped by scope requirement.
Inputs: no named parameters.
query_analyticsQuery AnalyticsA
Query mock analytics data including daily active users, revenue, churn rate, and feature usage across a 30-day window.
Inputs: metric (required), endDate, startDate.
random_numberRandom NumberA
Generates a random integer in an inclusive range [min, max].
Inputs: max, min.
read_fileRead FileA
Reads the contents of a mock file from the file system. Specify the full path like '/project/src/server.ts'.
Inputs: path (required).
reload_configReload ConfigurationA
Hot-reloads the OAuth provider configuration from disk. Useful after updating scopes or provider settings.
Inputs: no named parameters.
search_documentsSearch DocumentsA
Full-text search across the mock knowledge base of 15+ articles covering Technology, Science, and Business topics.
Inputs: query (required), category, maxResults. Returns: results, totalMatches
summarize_listSummarize ListA
Computes basic statistics (count, sum, min, max, mean, median, standard deviation) over a list of numbers.
Inputs: numbers (required). Returns: max, min, sum, mean, +3 more
update_documentUpdate DocumentB
Updates an existing knowledge base article.
Inputs: id (required), tags, title, content.
update_projectUpdate ProjectB
Updates a project's status, progress, or details.
Inputs: id (required), status, deadline, progressPercent.
upload_fileUpload FileA
Creates or overwrites a file in the mock file system. Specify the full path and content.
Inputs: path (required), content (required).
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 18 tools
Each tool performs a distinct action on a distinct resource (e.g., echo vs calculate vs read_file vs create_project). Descriptions are explicit and leave no ambiguity about what each tool does.
The vast majority of tools follow a verb_noun pattern (read_file, create_document, list_projects, upload_file). A few outliers (echo, calculate, current_time, random_number) use bare verbs or noun phrases, causing minor inconsistency.
With 18 tools, the set feels bloated for an OAuth-oriented server. Many tools (echo, calculate, current_time, random_number, summarize_list) are generic utilities unrelated to OAuth, making the count disproportionate to the server's stated purpose.
The server lacks any OAuth-specific tools such as authorize, token, or client management. Even within the mock domains (files, documents, projects), operations like delete or single-item retrieval are missing, leaving significant gaps in the tool surface.
Maintenance
Related MCP Connectors
Self-hosted federated MCP gateway: one OAuth 2.1 MCP server in front of N apps, user-level scopes.
Demo banking MCP server: accounts, transactions and payments. OAuth 2.1 with DPoP.
OAuth-protected, read-only-by-default MCP server for provenance-labeled QuillCaddie project memory.
Model Context Protocol server for the Apideck Unified API. Connect any MCP-compatible agent framework to 100+ accounting systems, HRIS platforms, file storage providers, and more through one integration. More information https://www.apideck.com/mcp-server
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceA server implementation of the Model Context Protocol (MCP) that provides REST API endpoints for managing and interacting with MCP resources.-
- AlicenseNot gradedqualityDmaintenanceA self-hostable OAuth 2.0 server designed for the Model-Context-Protocol (MCP) that enables you to secure your MCP applications with a robust implementation you control.4,744 npm113ISC
- AlicenseNot gradedqualityDmaintenanceA comprehensive reference implementation demonstrating all features of the Model Context Protocol (MCP) specification, serving as documentation, learning resource, and testing tool for MCP implementations.1MIT
- AlicenseNot gradedqualityDmaintenanceA reference implementation of an MCP server built with Express that integrates full OAuth 2.1 authorization and RFC9728 protected resource metadata. It enables secure, authenticated communication between MCP clients and servers using streamable HTTP transport and built-in authorization flows.ISC