This guide covers the OAuth 2.1 authentication implementation for the Twenty MCP Server, following the MCP Authentication Specification.
- Quick Start
- Architecture Overview
- Setup and Configuration
- OAuth Flow
- API Endpoints
- Client Integration
- Security Considerations
- Troubleshooting
npm run setup:oauthThis interactive CLI will guide you through:
- Enabling OAuth authentication
- Configuring Clerk credentials
- Setting up encryption
- Configuring default API keys
npm start# Check OAuth protected resource metadata
curl http://localhost:3000/.well-known/oauth-protected-resource
# Check authorization server metadata
curl http://localhost:3000/.well-known/oauth-authorization-serversequenceDiagram
participant Client as MCP Client
participant Server as Twenty MCP Server
participant Clerk as Clerk Auth
participant Twenty as Twenty CRM
Client->>Server: 1. Discover OAuth endpoints
Server->>Client: 2. Return discovery metadata
Client->>Clerk: 3. OAuth authorization flow
Clerk->>Client: 4. Return access token
Client->>Server: 5. Store API key (with Bearer token)
Server->>Clerk: 6. Validate token & get user ID
Server->>Server: 7. Encrypt & store user's Twenty API key
Client->>Server: 8. MCP request (with Bearer token)
Server->>Clerk: 9. Validate token
Server->>Server: 10. Decrypt user's API key
Server->>Twenty: 11. Make Twenty API call
Twenty->>Server: 12. Return data
Server->>Client: 13. Return MCP response
| Variable | Required | Description |
|---|---|---|
AUTH_ENABLED |
No | Enable OAuth authentication (default: false) |
REQUIRE_AUTH |
No | Require auth for all requests (default: false) |
AUTH_PROVIDER |
No | Auth provider (currently only 'clerk') |
CLERK_PUBLISHABLE_KEY |
If auth enabled | Clerk publishable key |
CLERK_SECRET_KEY |
If auth enabled | Clerk secret key |
CLERK_DOMAIN |
If auth enabled | Clerk domain (auto-detected if not set) |
API_KEY_ENCRYPTION_SECRET |
If auth enabled | 32+ character secret for encrypting API keys |
TWENTY_API_KEY |
No | Global Twenty API key (fallback) |
TWENTY_BASE_URL |
No | Twenty CRM base URL (default: https://api.twenty.com) |
Create a .env file:
# OAuth Configuration
AUTH_ENABLED=true
REQUIRE_AUTH=false
AUTH_PROVIDER=clerk
# Clerk Configuration
CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
CLERK_DOMAIN=your-app.clerk.accounts.dev
# Security
API_KEY_ENCRYPTION_SECRET=your-32-byte-hex-string-here
# Optional: Global Twenty API Key
TWENTY_API_KEY=your-twenty-api-key
TWENTY_BASE_URL=https://api.twenty.comMCP clients discover OAuth capabilities by calling:
GET /.well-known/oauth-protected-resourceResponse:
{
"resource": "http://localhost:3000",
"authorization_servers": ["https://your-app.clerk.accounts.dev"],
"bearer_methods_supported": ["header"],
"scopes_supported": ["twenty:read", "twenty:write"]
}GET /.well-known/oauth-authorization-serverResponse:
{
"issuer": "https://your-app.clerk.accounts.dev",
"authorization_endpoint": "https://your-app.clerk.accounts.dev/oauth/authorize",
"token_endpoint": "https://your-app.clerk.accounts.dev/oauth/token",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"code_challenge_methods_supported": ["S256"]
}Client redirects user to Clerk's authorization endpoint:
https://your-app.clerk.accounts.dev/oauth/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=YOUR_REDIRECT_URI
&scope=twenty:read+twenty:write
&code_challenge=CHALLENGE
&code_challenge_method=S256
Client exchanges authorization code for access token:
POST https://your-app.clerk.accounts.dev/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTHORIZATION_CODE
&client_id=YOUR_CLIENT_ID
&code_verifier=VERIFIER
&redirect_uri=YOUR_REDIRECT_URIStore user's Twenty API key (one-time setup):
POST /api/keys
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
{
"apiKey": "user-twenty-api-key",
"baseUrl": "https://api.twenty.com"
}Make MCP requests with Bearer token:
POST /mcp
Authorization: Bearer ACCESS_TOKEN
Content-Type: application/json
{
"jsonrpc": "2.0",
"method": "initialize",
"params": {},
"id": 1
}GET /healthReturns server status and auth configuration.
GET /.well-known/oauth-protected-resource- Protected resource metadataGET /.well-known/oauth-authorization-server- Authorization server metadata
All endpoints require valid Bearer token.
GET /api/keys
Authorization: Bearer TOKENResponse:
{
"hasKey": true,
"updatedAt": "2024-01-01T00:00:00Z",
"baseUrl": "https://api.twenty.com"
}POST /api/keys
Authorization: Bearer TOKEN
Content-Type: application/json
{
"apiKey": "your-twenty-api-key",
"baseUrl": "https://api.twenty.com"
}DELETE /api/keys
Authorization: Bearer TOKENPOST /mcp[?apiKey=key]
Authorization: Bearer TOKEN (if auth enabled)
Content-Type: application/json
{
"jsonrpc": "2.0",
"method": "method_name",
"params": {},
"id": 1
}import { Client } from '@modelcontextprotocol/sdk/client/index.js';
// 1. Discover OAuth endpoints
const discovery = await fetch('http://localhost:3000/.well-known/oauth-protected-resource');
const metadata = await discovery.json();
// 2. Perform OAuth flow (implementation depends on client)
const accessToken = await performOAuthFlow(metadata.authorization_servers[0]);
// 3. Configure API key (one-time)
await fetch('http://localhost:3000/api/keys', {
method: 'POST',
headers: {
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
apiKey: 'user-twenty-api-key',
baseUrl: 'https://api.twenty.com'
})
});
// 4. Create authenticated MCP client
const client = new Client({
name: 'my-client',
version: '1.0.0'
}, {
capabilities: {}
});
// 5. Connect with Bearer token
await client.connect(transport, {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});The server maintains backward compatibility with non-authenticated requests:
// Works when AUTH_ENABLED=false or REQUIRE_AUTH=false
const url = 'http://localhost:3000/mcp?apiKey=your-api-key';
await client.connect(httpTransport(url));- User API keys are encrypted using AES-256-GCM
- Unique initialization vector (IV) for each encryption
- Authentication tags prevent tampering
- Keys are stored in Clerk's secure
privateMetadata
- Bearer tokens are validated with Clerk on every request
- Session status checked for active sessions
- JWT signatures verified
- Tokens cached briefly to reduce Clerk API calls
- HTTPS required in production
- CORS headers configured for cross-origin requests
- Secure HTTP headers recommended
# Secure environment variables
chmod 600 .env
# Use strong encryption secret
openssl rand -hex 32Error: Authentication failed - mock token not accepted
Solution: Use real Clerk tokens, not mock tokens.
Error: No API key configured
Solution: Configure API key via /api/keys endpoint after authentication.
Error: API_KEY_ENCRYPTION_SECRET environment variable is required
Solution: Set a 32+ character encryption secret in environment variables.
Error: CORS policy blocked request
Solution: Server includes CORS headers, but check client configuration.
Enable debug logging:
DEBUG=clerk:*
NODE_ENV=developmentcurl http://localhost:3000/healthShould return:
{
"status": "healthy",
"service": "twenty-mcp-server",
"authEnabled": true
}# Test protected resource metadata
curl -v http://localhost:3000/.well-known/oauth-protected-resource
# Should return 200 OK when auth is enabled
# Should return 404 when auth is disabled# Test with real Clerk token
export ACCESS_TOKEN="your-clerk-token"
curl -H "Authorization: Bearer $ACCESS_TOKEN" \
http://localhost:3000/api/keys-
Backup existing configuration:
cp .env .env.backup
-
Run OAuth setup:
npm run setup:oauth
-
Test backward compatibility:
# Should still work curl "http://localhost:3000/mcp?apiKey=your-key"
-
Enable required auth (optional):
REQUIRE_AUTH=true
-
Update Clerk keys from test to live:
CLERK_PUBLISHABLE_KEY=pk_live_... CLERK_SECRET_KEY=sk_live_...
-
Use strong encryption secret:
openssl rand -hex 32
-
Enable HTTPS and update server URL:
MCP_SERVER_URL=https://your-domain.com
-
Review security headers in production deployment.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- MCP Spec: Model Context Protocol
- Clerk Docs: Clerk Documentation