refactor, improve docs
This commit is contained in:
parent
dc6c2a00e9
commit
112997d352
2 changed files with 30079 additions and 1640 deletions
|
|
@ -1,94 +0,0 @@
|
|||
# MCP Standalone Authorization Server
|
||||
|
||||
This is a demonstration OAuth 2.0 authorization server for MCP's separate authentication mode.
|
||||
|
||||
## Purpose
|
||||
|
||||
This server demonstrates how MCP servers can delegate authentication to a separate authorization server. See the main [README Authentication Modes](../README.md#authentication-modes) section for a complete overview of integrated vs separate modes.
|
||||
|
||||
In production environments, you would typically use established OAuth providers like:
|
||||
- Auth0, Okta
|
||||
- Google OAuth, GitHub OAuth
|
||||
- Microsoft Azure AD, AWS Cognito
|
||||
|
||||
## Architecture
|
||||
|
||||
For detailed architecture information and OAuth flow analysis, see:
|
||||
- [Authentication Modes](../README.md#authentication-modes) - Overview and comparison
|
||||
- [OAuth 2.0 + PKCE Flow Analysis](../README.md#oauth-20--pkce-flow-analysis) - Step-by-step flow breakdown
|
||||
- [Authentication Architecture](../README.md#authentication-architecture) - Visual diagrams
|
||||
|
||||
This auth server specifically implements the "Auth Server" component in the separate mode architecture diagram.
|
||||
|
||||
## Endpoints
|
||||
|
||||
- `/.well-known/oauth-authorization-server` - OAuth 2.0 server metadata
|
||||
- `/authorize` - Authorization endpoint
|
||||
- `/token` - Token endpoint
|
||||
- `/register` - Dynamic client registration
|
||||
- `/introspect` - Token introspection (for MCP server validation)
|
||||
- `/fakeupstreamauth/authorize` - Fake upstream auth page (demo only)
|
||||
- `/fakeupstreamauth/callback` - Fake upstream callback (demo only)
|
||||
- `/health` - Health check endpoint
|
||||
|
||||
## Development
|
||||
|
||||
This server shares Redis with the MCP server for development convenience.
|
||||
In production, these would typically be separate.
|
||||
|
||||
## Running the Auth Server
|
||||
|
||||
### Standalone
|
||||
```bash
|
||||
# From the repository root
|
||||
npm run dev:auth-server
|
||||
```
|
||||
|
||||
### With MCP Server (Separate Mode)
|
||||
```bash
|
||||
# Start both servers together
|
||||
npm run dev:with-separate-auth
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Health Check
|
||||
```bash
|
||||
curl http://localhost:3001/health
|
||||
```
|
||||
|
||||
### OAuth Metadata
|
||||
```bash
|
||||
curl http://localhost:3001/.well-known/oauth-authorization-server
|
||||
```
|
||||
|
||||
### With MCP Inspector
|
||||
See the main [Testing with MCP Inspector](../README.md#testing-with-mcp-inspector) section for complete testing instructions for both modes.
|
||||
|
||||
**Quick test for this auth server:**
|
||||
1. Start this auth server: `npm run dev:auth-server`
|
||||
2. Start MCP server in separate mode: `AUTH_MODE=separate npm run dev`
|
||||
3. Follow the separate mode testing steps in the main README
|
||||
|
||||
## Configuration
|
||||
|
||||
The auth server uses the same configuration system as the main server. See [Configuration](../README.md#configuration) in the main README for complete environment variable documentation.
|
||||
|
||||
**Auth server specific variables:**
|
||||
- `AUTH_SERVER_PORT` - Port to run on (default: 3001)
|
||||
- `AUTH_SERVER_URL` - Base URL (default: http://localhost:3001)
|
||||
- `REDIS_URL` - Redis connection (shared with MCP server)
|
||||
|
||||
## Production Considerations
|
||||
|
||||
**This server is for demonstration only.** In production, use established OAuth providers.
|
||||
|
||||
For comprehensive security and deployment guidance, see:
|
||||
- [Security](../README.md#security) - Security measures and best practices
|
||||
- [Configuration](../README.md#configuration) - Environment setup
|
||||
- [Monitoring & Debugging](../README.md#monitoring--debugging) - Operational guidance
|
||||
|
||||
**Production replacement options:**
|
||||
- Corporate SSO (Auth0, Okta)
|
||||
- Cloud providers (AWS Cognito, Azure AD)
|
||||
- Social providers (Google OAuth, GitHub OAuth)
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';
|
||||
import { EverythingAuthProvider } from '../src/auth/provider.js';
|
||||
import { handleFakeAuthorize, handleFakeAuthorizeRedirect } from '../src/handlers/fakeauth.js';
|
||||
import { redisClient } from '../src/redis.js';
|
||||
import { logger } from '../src/utils/logger.js';
|
||||
import { AUTH_SERVER_PORT, AUTH_SERVER_URL, BASE_URI } from '../src/config.js';
|
||||
|
||||
const app = express();
|
||||
|
||||
console.log('=====================================');
|
||||
console.log('MCP Demonstration Authorization Server');
|
||||
console.log('=====================================');
|
||||
console.log('This standalone server demonstrates OAuth 2.0');
|
||||
console.log('authorization separate from the MCP resource server');
|
||||
console.log('');
|
||||
console.log('This is for demonstration purposes only.');
|
||||
console.log('In production, you would use a real OAuth provider');
|
||||
console.log('like Auth0, Okta, Google, GitHub, etc.');
|
||||
console.log('=====================================');
|
||||
|
||||
// CORS for Inspector and MCP server
|
||||
app.use(cors({
|
||||
origin: true,
|
||||
credentials: true
|
||||
}));
|
||||
|
||||
app.use(express.json());
|
||||
app.use(logger.middleware());
|
||||
|
||||
// Health check endpoint
|
||||
app.get('/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'healthy',
|
||||
mode: 'authorization-server',
|
||||
endpoints: {
|
||||
metadata: `${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`,
|
||||
authorize: `${AUTH_SERVER_URL}/authorize`,
|
||||
token: `${AUTH_SERVER_URL}/token`,
|
||||
register: `${AUTH_SERVER_URL}/register`,
|
||||
introspect: `${AUTH_SERVER_URL}/introspect`
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Create auth provider instance for reuse
|
||||
const authProvider = new EverythingAuthProvider();
|
||||
|
||||
// OAuth endpoints via SDK's mcpAuthRouter
|
||||
app.use(mcpAuthRouter({
|
||||
provider: authProvider,
|
||||
issuerUrl: new URL(AUTH_SERVER_URL),
|
||||
tokenOptions: {
|
||||
rateLimit: { windowMs: 5000, limit: 100 }
|
||||
},
|
||||
clientRegistrationOptions: {
|
||||
rateLimit: { windowMs: 60000, limit: 10 }
|
||||
}
|
||||
}));
|
||||
|
||||
// Rate limiting for custom endpoints
|
||||
const introspectRateLimit = rateLimit({
|
||||
windowMs: 5 * 60 * 1000, // 5 minutes
|
||||
limit: 100, // 100 introspections per 5 minutes
|
||||
message: { error: 'too_many_requests', error_description: 'Token introspection rate limit exceeded' }
|
||||
});
|
||||
|
||||
const fakeAuthRateLimit = rateLimit({
|
||||
windowMs: 60 * 1000, // 1 minute
|
||||
limit: 20, // 20 auth attempts per minute
|
||||
message: { error: 'too_many_requests', error_description: 'Authentication rate limit exceeded' }
|
||||
});
|
||||
|
||||
const staticFileRateLimit = rateLimit({
|
||||
windowMs: 10 * 60 * 1000, // 10 minutes
|
||||
limit: 25, // 25 requests per 10 minutes for static files
|
||||
message: { error: 'too_many_requests', error_description: 'Static file rate limit exceeded' }
|
||||
});
|
||||
|
||||
// Token introspection endpoint (RFC 7662)
|
||||
app.post('/introspect', introspectRateLimit, express.urlencoded({ extended: false }), async (req, res) => {
|
||||
try {
|
||||
const { token } = req.body;
|
||||
|
||||
if (!token) {
|
||||
return res.status(400).json({ error: 'invalid_request', error_description: 'Missing token parameter' });
|
||||
}
|
||||
|
||||
// Verify the token using the auth provider
|
||||
const authInfo = await authProvider.verifyAccessToken(token);
|
||||
|
||||
// Return RFC 7662 compliant response
|
||||
res.json({
|
||||
active: true,
|
||||
client_id: authInfo.clientId,
|
||||
scope: authInfo.scopes.join(' '),
|
||||
exp: authInfo.expiresAt,
|
||||
sub: authInfo.extra?.userId || 'unknown',
|
||||
userId: authInfo.extra?.userId, // Custom field for our implementation
|
||||
username: authInfo.extra?.username,
|
||||
iss: AUTH_SERVER_URL,
|
||||
aud: BASE_URI, // The resource server URL this token is intended for
|
||||
token_type: 'Bearer'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.debug('Token introspection failed', { error: (error as Error).message });
|
||||
|
||||
// Return inactive token response (don't leak error details)
|
||||
res.json({
|
||||
active: false
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Fake upstream auth endpoints (for user authentication simulation)
|
||||
app.get('/fakeupstreamauth/authorize', fakeAuthRateLimit, cors(), handleFakeAuthorize);
|
||||
app.get('/fakeupstreamauth/callback', fakeAuthRateLimit, cors(), handleFakeAuthorizeRedirect);
|
||||
|
||||
// Static assets (for auth page styling)
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
app.get('/mcp-logo.png', staticFileRateLimit, (req, res) => {
|
||||
// Serve from the main server's static directory
|
||||
const logoPath = path.join(__dirname, '../src/static/mcp.png');
|
||||
res.sendFile(logoPath);
|
||||
});
|
||||
|
||||
// Connect to Redis (shared with MCP server in dev)
|
||||
try {
|
||||
await redisClient.connect();
|
||||
logger.info('Connected to Redis', { url: redisClient.options?.url });
|
||||
} catch (error) {
|
||||
logger.error('Could not connect to Redis', error as Error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
app.listen(AUTH_SERVER_PORT, () => {
|
||||
logger.info('Authorization server started', {
|
||||
port: AUTH_SERVER_PORT,
|
||||
url: AUTH_SERVER_URL,
|
||||
endpoints: {
|
||||
metadata: `${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`,
|
||||
authorize: `${AUTH_SERVER_URL}/authorize`,
|
||||
token: `${AUTH_SERVER_URL}/token`,
|
||||
register: `${AUTH_SERVER_URL}/register`,
|
||||
introspect: `${AUTH_SERVER_URL}/introspect`
|
||||
}
|
||||
});
|
||||
|
||||
console.log('');
|
||||
console.log('🚀 Auth server ready! Test with:');
|
||||
console.log(` curl ${AUTH_SERVER_URL}/health`);
|
||||
console.log(` curl ${AUTH_SERVER_URL}/.well-known/oauth-authorization-server`);
|
||||
console.log('');
|
||||
console.log('💡 To test separate mode:');
|
||||
console.log(' 1. Keep this server running');
|
||||
console.log(' 2. In another terminal: AUTH_MODE=separate npm run dev');
|
||||
console.log(' 3. Connect Inspector to http://localhost:3232/mcp');
|
||||
});
|
||||
Loading…
Reference in a new issue