loading…
Search for a command to run...
loading…
Express middleware library for MCP (Model Context Protocol) OAuth authentication
Express middleware library for MCP (Model Context Protocol) OAuth authentication
Universal OAuth middleware library for MCP (Model Context Protocol) servers with support for any OAuth provider.
This library provides OAuth authentication for MCP servers using a flexible connector pattern. It handles the complete OAuth flow for any OAuth provider and provides an authenticated /mcp endpoint where you create your own MCP server with tools. Built using the official MCP TypeScript SDK.
app.use()npm install
import express from "express"
import { Server } from "@modelcontextprotocol/sdk/server/index.js"
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"
import { McpOAuth } from "mcp-s-oauth"
import type { McpOAuthConfig } from "mcp-s-oauth"
const app = express()
// Choose your OAuth provider connector
import { githubConnector } from "mcp-s-oauth"
// or import { googleConnector } from "mcp-s-oauth"
// or create your own custom connector
// Configure OAuth with any provider
const config: McpOAuthConfig = {
baseUrl: "http://localhost:3000",
clientId: "your-oauth-client-id",
clientSecret: "your-oauth-client-secret",
connector: githubConnector // or any other connector
}
// Create your MCP handler - this is where YOU create the MCP server
const mcpHandler = async (req: express.Request, res: express.Response, { authInfo }) => {
// Access the OAuth token from any provider
const oauthToken = authInfo.token
// Create transport and your MCP server
const transport = new StreamableHTTPServerTransport(/* options */)
const mcpServer = new Server({ name: "my-server", version: "1.0.0" })
// Register your tools (customize based on your OAuth provider)
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: "get_profile",
description: "Get authenticated user profile",
inputSchema: { type: "object" }
}]
}))
mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name === "get_profile") {
// Call your OAuth provider's API using the token
// Example for GitHub: https://api.github.com/user
// Example for Google: https://www.googleapis.com/oauth2/v2/userinfo
const apiUrl = "https://api.github.com/user" // adjust for your provider
const response = await fetch(apiUrl, {
headers: { "Authorization": `Bearer ${authInfo.token}` }
})
const userData = await response.json()
return {
content: [{ type: "text", text: `Profile: ${JSON.stringify(userData, null, 2)}` }]
}
}
})
// Handle the MCP request
await mcpServer.connect(transport)
await transport.handleRequest(req, res, req.body)
}
// Use the library
const mcpOAuth = McpOAuth(config, mcpHandler)
app.use("/", mcpOAuth.router)
app.listen(3000)
${baseUrl}/oauth/callback (e.g., http://localhost:3000/oauth/callback)${baseUrl}/oauth/callback (e.g., http://localhost:3000/oauth/callback)For any OAuth 2.0 provider, you'll need:
${baseUrl}/oauth/callbackThis library includes pre-built connectors for popular OAuth providers with minimal scopes for enhanced security:
| Service | Connector |
|---|---|
slackConnector |
|
gmailConnector |
|
googleCalendarConnector |
|
jiraConnector |
|
trelloConnector |
|
asanaConnector |
|
notionConnector |
|
mondayConnector |
|
githubConnector |
|
gitlabConnector |
|
googleWorkspaceConnector |
|
googleDriveConnector |
|
googleSheetsConnector |
|
googleFormsConnector |
|
googleSlidesConnector |
|
salesforceConnector |
|
figmaConnector |
|
zeplinConnector |
|
amplitudeConnector |
|
googleAnalyticsConnector |
|
googleMapsConnector |
|
discordConnector |
|
spotifyConnector |
|
twitterConnector |
import { githubConnector } from "mcp-s-oauth"
const config: McpOAuthConfig = {
baseUrl: "http://localhost:3000",
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
connector: githubConnector,
}
For any OAuth provider not listed above:
import type { Connector } from "mcp-s-oauth"
const myCustomConnector: Connector = {
authUrl: "https://your-provider.com/oauth/authorize",
tokenUrl: "https://your-provider.com/oauth/token",
scopes: ["read", "write"],
codeExchangeConfig: {
isForm: true,
modelCredentialsMapping: `{
"access_token": access_token,
"expires_at": $fromMillis($millis() + expires_in * 1000),
"refresh_token": refresh_token,
"scope": scope,
"token_type": token_type
}`,
},
authInitUrlParams: {
prompt: "consent",
},
}
The Connector interface allows you to integrate any OAuth 2.0 provider. Here's how to create your own:
export interface Connector {
authUrl?: string // OAuth authorization endpoint
tokenUrl?: string // OAuth token exchange endpoint
refreshTokenUrl?: string // Token refresh endpoint (defaults to tokenUrl)
scopes?: string[] | readonly string[] // OAuth scopes to request
codeExchangeConfig?: {
modelCredentialsMapping?: JsonataString<OAuthCredentials> | ((config: any) => OAuthCredentials)
isForm?: boolean // Use form encoding vs JSON for token exchange
authorizationMapping?: JsonataString<string> | ((config: any) => string)
}
authInitUrlParams?: Record<string, string> // Additional OAuth params
}
Find OAuth Documentation for your provider (authorization URL, token URL, scopes)
Create Connector File:
// src/connectors/my-provider.ts
import type { Connector } from "../types/connector.types.js"
export const myProviderConnector: Connector = {
authUrl: "https://api.myprovider.com/oauth/authorize",
tokenUrl: "https://api.myprovider.com/oauth/token",
scopes: ["read", "write"],
codeExchangeConfig: {
isForm: true, // Most providers use form encoding
modelCredentialsMapping: `{
"access_token": access_token,
"expires_at": $fromMillis($millis() + expires_in * 1000),
"refresh_token": refresh_token,
"scope": scope,
"token_type": token_type
}`,
},
}
// Provider requires special auth parameters
export const specialProviderConnector: Connector = {
authUrl: "https://special.com/oauth/authorize",
tokenUrl: "https://special.com/oauth/token",
scopes: ["user:read"],
authInitUrlParams: {
access_type: "offline",
prompt: "consent",
response_mode: "query",
},
codeExchangeConfig: {
isForm: false, // This provider uses JSON
},
}
export const complexProviderConnector: Connector = {
authUrl: "https://complex.com/oauth/authorize",
tokenUrl: "https://complex.com/oauth/token",
scopes: ["api"],
codeExchangeConfig: {
isForm: true,
// Use function for complex response mapping
modelCredentialsMapping: (tokenResponse) => ({
access_token: tokenResponse.accessToken, // Different field name
expires_at: new Date(Date.now() + tokenResponse.expiresIn * 1000).toISOString(),
refresh_token: tokenResponse.refreshToken,
refresh_token_expires_at: null,
scope: tokenResponse.scope,
token_type: "Bearer",
}),
},
}
| Provider | Auth URL | Token URL | Form Encoding | Special Notes |
|---|---|---|---|---|
| GitHub | /login/oauth/authorize |
/login/oauth/access_token |
❌ JSON | Simple flow |
/o/oauth2/v2/auth |
/oauth2/token |
✅ Form | Use access_type: offline |
|
| Discord | /api/oauth2/authorize |
/api/oauth2/token |
✅ Form | Standard OAuth |
/i/oauth2/authorize |
/2/oauth2/token |
✅ Form | Requires PKCE | |
/authorization |
/accessToken |
✅ Form | Different field names |
const config: McpOAuthConfig = {
baseUrl: "http://localhost:3000",
clientId: process.env.MY_PROVIDER_CLIENT_ID!,
clientSecret: process.env.MY_PROVIDER_CLIENT_SECRET!,
connector: myProviderConnector,
}
Test OAuth flow:
/auth/authorizeCommon Issues:
The tools you create depend on your OAuth provider and their APIs. The library provides the OAuth token - you implement the tools. Here's a complete GitHub example:
// In your mcpHandler function
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "github_me",
description: "Get authenticated user's GitHub profile",
inputSchema: { type: "object", properties: {} }
},
{
name: "github_repos",
description: "List user's repositories",
inputSchema: {
type: "object",
properties: {
type: { type: "string", enum: ["all", "owner", "member"] }
}
}
},
{
name: "github_issues",
description: "List user's issues",
inputSchema: {
type: "object",
properties: {
state: { type: "string", enum: ["open", "closed", "all"] }
}
}
}
]
}))
mcpServer.setRequestHandler(CallToolRequestSchema, async (request, { authInfo }) => {
if (request.params.name === "github_me") {
const response = await fetch("https://api.github.com/user", {
headers: { "Authorization": `Bearer ${authInfo.token}` }
})
return { content: [{ type: "text", text: await response.text() }] }
}
if (request.params.name === "github_repos") {
const { type = "all" } = request.params.arguments || {}
const response = await fetch(`https://api.github.com/user/repos?type=${type}`, {
headers: { "Authorization": `Bearer ${authInfo.token}` }
})
return { content: [{ type: "text", text: await response.text() }] }
}
if (request.params.name === "github_issues") {
const { state = "open" } = request.params.arguments || {}
const response = await fetch(`https://api.github.com/issues?state=${state}`, {
headers: { "Authorization": `Bearer ${authInfo.token}` }
})
return { content: [{ type: "text", text: await response.text() }] }
}
})
For other OAuth providers, follow the same pattern using their respective APIs:
https://slack.com/api/ endpointshttps://api.notion.com/v1/ endpointsYour OAuth-authenticated MCP server implements the MCP (Model Context Protocol) and can be used with any MCP-compatible client:
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
const client = new Client({
name: "oauth-mcp-client",
version: "1.0.0"
})
const transport = new StreamableHTTPClientTransport(
new URL("http://localhost:3000/mcp")
)
await client.connect(transport)
// List available tools (depends on your implementation)
const tools = await client.listTools()
console.log("Available tools:", tools)
// Call any tool you've implemented
const result = await client.callTool({
name: "get_profile", // or any tool name you've implemented
arguments: {}
})
console.log("Result:", result)
/auth/authorize to initiate OAuth with your provider/mcpmcp-session-id headerTest the MCP server using the official SDK client:
import { Client } from "@modelcontextprotocol/sdk/client/index.js"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"
// Connect to the server
const client = new Client({ name: "test-client", version: "1.0.0" })
const transport = new StreamableHTTPClientTransport(
new URL("http://localhost:3000/mcp"),
{
requestInit: {
headers: {
'Authorization': 'Bearer your-oauth-token-here'
}
}
}
)
await client.connect(transport)
// Test listing tools
const tools = await client.listTools()
console.log("Available tools:", tools.tools)
// Test calling any tool you've implemented
const result = await client.callTool({
name: "get_profile", // or any tool name you've implemented
arguments: {}
})
console.log("Result:", result.content)
src/
├── index.ts # Library exports
├── server.ts # MCP server implementation
├── lib.ts # Core utilities
├── connectors/ # OAuth provider connectors
├── services/ # Database & auth services
└── types/ # TypeScript types
# Install dependencies
npm install
# Development mode with auto-reload
npm run dev
# Build for production
npm run build
# Run production build
npm start
# Run tests
npm test
# Lint code
npm run lint
# Format code
npm run format
To add new MCP tools to your server:
ListToolsRequestSchema handlerCallToolRequestSchema handler Example (provider-agnostic):
// In ListToolsRequestSchema handler
{
name: "get_user_data",
description: "Get user data from OAuth provider",
inputSchema: {
type: "object",
properties: {
fields: {
type: "array",
items: { type: "string" },
description: "Fields to retrieve"
}
}
}
}
// In CallToolRequestSchema handler
if (name === "get_user_data") {
const { fields = [] } = request.params.arguments || {}
// Use the OAuth token to call your provider's API
const apiUrl = "https://api.your-provider.com/user" // adjust for your provider
const response = await fetch(apiUrl, {
headers: {
"Authorization": `Bearer ${authInfo.token}`,
"Accept": "application/json"
}
})
const userData = await response.json()
return {
content: [{
type: "text",
text: JSON.stringify(userData, null, 2)
}]
}
}
"Missing required environment variables"
CLIENT_ID and CLIENT_SECRET are setOAuth callback errors
BASE_URL matches your OAuth app configuration${baseUrl}/oauth/callbackToken exchange errors
isForm setting in your connector - most providers use form encodingtokenUrl is correct for your providerAPI rate limits
MCP client connection issues
/mcpConnector issues
Enable detailed logging by setting environment variables:
DEBUG=mcp:*
NODE_ENV=development
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
MIT License - see LICENSE file for details.
Visit webrix.ai for our fully managed hosting solution with advanced features:
Zero Configuration: Get started in seconds without any setup Enterprise-grade Security: Advanced SSO authentication for all MCP interactions 20+ Pre-built Connectors: Fast plug-and-play integration with hundreds of tools Roles & Permissions: Granular access control with custom role definitions Monitoring & Analytics: Real-time insights into your MCP usage High Availability: 99.9% uptime SLA with global CDN Premium Support: Direct access to our engineering team Custom Integrations: Build and deploy custom MCP connectors
Добавь это в claude_desktop_config.json и перезапусти Claude Desktop.
{
"mcpServers": {
"s-oauth": {
"command": "npx",
"args": [
"-y",
"mcp-s-oauth"
]
}
}
}