MCP S OAuth
Universal OAuth middleware library for MCP (Model Context Protocol) servers with support for any OAuth provider.
Overview
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.
Features
- 🔐 Universal OAuth Support - Works with 20+ OAuth providers including GitHub, Google, Slack, and more
- 🔌 Connector Pattern - Pre-built connectors for popular services + easily add support for new OAuth providers
- 🚀 Express Middleware - Simple integration with
app.use()
- 🛠️ Bring Your Own MCP Server - Create MCP server in your handler function
- 📊 Session Management - Automatic session handling via MCP SDK
- 🌐 Authenticated Context - Access OAuth tokens in your tools
- 🎯 Production Ready - Built with official MCP SDK components
- 🔑 Minimal Scopes - Uses minimal OAuth scopes for enhanced security
Installation
npm install
Quick Start
Basic Usage
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)
OAuth Provider Setup
GitHub OAuth App Setup
- Go to GitHub Developer Settings
- Create a new OAuth App:
- Authorization callback URL:
${baseUrl}/oauth/callback
(e.g.,http://localhost:3000/oauth/callback
)
- Authorization callback URL:
- Use the Client ID and Secret in your config
Google OAuth App Setup
- Go to Google Cloud Console
- Create a new project or select existing
- Enable the "Google+ API" or "People API"
- Create OAuth 2.0 credentials:
- Authorized redirect URIs:
${baseUrl}/oauth/callback
(e.g.,http://localhost:3000/oauth/callback
)
- Authorized redirect URIs:
- Use the Client ID and Secret in your config
Custom OAuth Provider Setup
For any OAuth 2.0 provider, you'll need:
- Client ID and Client Secret from your provider
- Authorization callback URL set to:
${baseUrl}/oauth/callback
Available Connectors
This 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 |
Example Usage with GitHub
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,
}
Custom Connector
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",
},
}
Creating Custom Connectors
The Connector
interface allows you to integrate any OAuth 2.0 provider. Here's how to create your own:
Connector Interface
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
}
Step-by-Step Guide
Find OAuth Documentation for your provider (authorization URL, token URL, scopes)
Create Connector File:
`
typescript // 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
}
,
},
}
3. **Handle Special Cases**:
```typescript
// 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
},
}
- Use Function for Complex Mapping:
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",
}),
},
}
Common OAuth Patterns
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 |
Testing Your Connector
Create test config:
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:
- Visit
/auth/authorize
- Complete OAuth on provider
- Check console logs for token exchange
- Test API calls with the token
- Visit
Common Issues:
- Form vs JSON: Check provider docs for token endpoint format
- Field Names: Response might use different field names
- Scopes: Ensure scopes are valid for your provider
- Callback URL: Must match OAuth app configuration
Example Tools Implementation
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:
GitHub Tools 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() }] }
}
})
Other Provider Examples
For other OAuth providers, follow the same pattern using their respective APIs:
- Slack: Use
https://slack.com/api/
endpoints - Google Services: Use Google API endpoints (Gmail, Drive, Sheets, etc.)
- Notion: Use
https://api.notion.com/v1/
endpoints - Jira: Use Atlassian REST API endpoints
- And more... - Each connector works with the provider's standard OAuth API
MCP Client Usage
Using with MCP Clients
Your 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)
Authentication Flow
- Start OAuth Flow: Make a request to
/auth/authorize
to initiate OAuth with your provider - User Authorization: User is redirected to OAuth provider to authorize your application
- Token Exchange: Provider redirects back with an authorization code
- MCP Access: Use the obtained token to make authenticated MCP requests to
/mcp
API Reference
Server Information
- GET / - Server information, available tools, and authentication endpoints
- GET /health - Health check endpoint
Authentication Endpoints
- POST /auth/authorize - Start OAuth flow with configured provider
- POST /auth/token - Exchange authorization code for access token
- GET /oauth/callback - OAuth callback handler (used by the provider)
MCP Protocol
- ALL /mcp - Main MCP endpoint supporting all MCP protocol methods
- Requires Bearer token authentication
- Supports session management with
mcp-session-id
header - Handles tools/list and tools/call requests
- Returns tools you've implemented in your mcpHandler
Testing the Server
Test 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)
Development
Project Structure
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
Building and Running
# 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
Adding New Tools
To add new MCP tools to your server:
- Update the tools list in
ListToolsRequestSchema
handler - Add tool implementation in
CallToolRequestSchema
handler - Use the OAuth token to call your provider's APIs
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)
}]
}
}
Security Considerations
- OAuth Secrets: Never commit OAuth client secrets to version control
- HTTPS: Use HTTPS in production for secure OAuth flows
- Environment Variables: Store all sensitive configuration in environment variables
- Token Storage: Tokens are managed by the MCP SDK OAuth provider
- OAuth App Configuration: Configure your OAuth app with correct callback URLs
- Scope Limitation: Request only the OAuth scopes your application needs
- Token Validation: The library handles token validation automatically
Troubleshooting
Common Issues
"Missing required environment variables"
- Ensure your OAuth provider's
CLIENT_ID
andCLIENT_SECRET
are set - Check environment variable names match your configuration
- Ensure your OAuth provider's
OAuth callback errors
- Verify your
BASE_URL
matches your OAuth app configuration - Check that Authorization callback URL is set to
${baseUrl}/oauth/callback
- Ensure your OAuth provider app is configured correctly
- Verify your
Token exchange errors
- Check
isForm
setting in your connector - most providers use form encoding - Verify
tokenUrl
is correct for your provider - Check if provider requires special headers or parameters
- Check
API rate limits
- Authenticated requests usually have higher rate limits
- Consider implementing caching for frequently accessed data
- Check your provider's rate limiting documentation
MCP client connection issues
- Ensure you're using the correct MCP endpoint URL:
/mcp
- Verify Bearer token authentication is properly configured
- Check that your tools are properly registered in the mcpHandler
- Ensure you're using the correct MCP endpoint URL:
Connector issues
- Verify OAuth URLs are correct for your provider
- Check scopes are valid for your provider
- Test OAuth flow manually in browser first
Debug Mode
Enable detailed logging by setting environment variables:
DEBUG=mcp:*
NODE_ENV=development
Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
License
MIT License - see LICENSE file for details.
Support
- 📚 Documentation
- 🐛 Issues
- 💬 Discussions