import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
CallToolResult,
ErrorCode,
McpError
} from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { Logger } from '../utils/logger.js';
import { BillingAnalyzer } from '../billing/billing-analyzer.js';
import { BillingClient } from '../billing/billing-client.js';
import { AuthManager, AuthSession } from '../auth/auth-manager.js';
// Tool parameter schemas
const CostAnalysisSchema = z.object({
accountId: z.string().optional(),
services: z.array(z.string()).optional(),
regions: z.array(z.string()).optional(),
startDate: z.string().optional(),
endDate: z.string().optional(),
minCost: z.number().optional(),
maxCost: z.number().optional(),
tags: z.record(z.string()).optional()
});
const UsageComparisonSchema = z.object({
currentPeriod: z.object({
startDate: z.string(),
endDate: z.string()
}),
previousPeriod: z.object({
startDate: z.string(),
endDate: z.string()
}),
accountId: z.string().optional(),
services: z.array(z.string()).optional(),
regions: z.array(z.string()).optional()
});
const TrendAnalysisSchema = z.object({
accountId: z.string().optional(),
service: z.string().optional(),
startDate: z.string(),
endDate: z.string(),
regions: z.array(z.string()).optional()
});
const AnomalyDetectionSchema = z.object({
accountId: z.string().optional(),
startDate: z.string(),
endDate: z.string(),
thresholdMultiplier: z.number().min(1).max(5).default(2.0),
services: z.array(z.string()).optional(),
regions: z.array(z.string()).optional()
});
const CostRankingSchema = z.object({
accountId: z.string().optional(),
startDate: z.string(),
endDate: z.string(),
services: z.array(z.string()).optional(),
regions: z.array(z.string()).optional(),
limit: z.number().min(1).max(50).default(10)
});
export interface ToolRegistry {
[key: string]: {
schema: Tool;
handler: (args: any) => Promise<CallToolResult>;
};
}
interface RateLimitEntry {
count: number;
resetTime: number;
}
interface ResourceLimits {
maxConcurrentRequests: number;
maxRecordsPerQuery: number;
requestTimeoutMs: number;
}
export class MCPServer {
private server: Server;
private logger: Logger;
private billingAnalyzer: BillingAnalyzer;
private billingClient: BillingClient;
private authManager: AuthManager;
private toolRegistry: ToolRegistry;
private rateLimitMap: Map<string, RateLimitEntry>;
private activeRequests: number;
private resourceLimits: ResourceLimits;
private authEnabled: boolean;
constructor(authEnabled: boolean = true) {
this.logger = Logger.getInstance();
this.billingAnalyzer = BillingAnalyzer.getInstance();
this.billingClient = BillingClient.getInstance();
this.authManager = AuthManager.getInstance();
this.authEnabled = authEnabled;
this.server = new Server({
name: 'aws-billing-mcp-server',
version: '1.0.0',
});
this.toolRegistry = {};
this.rateLimitMap = new Map();
this.activeRequests = 0;
this.resourceLimits = {
maxConcurrentRequests: 10,
maxRecordsPerQuery: 10000,
requestTimeoutMs: 30000
};
this.setupToolRegistry();
this.setupHandlers();
if (this.authEnabled) {
this.authManager.startSessionCleanup();
this.logger.info('Authentication enabled for MCP server');
} else {
this.logger.warn('Authentication disabled for MCP server');
}
}
private setupToolRegistry(): void {
// Cost Analysis Tool
this.toolRegistry['analyze_costs'] = {
schema: {
name: 'analyze_costs',
description: 'Analyze AWS billing costs with filtering and aggregation capabilities',
inputSchema: {
type: 'object',
properties: {
accountId: {
type: 'string',
description: 'AWS account ID to filter by'
},
services: {
type: 'array',
items: { type: 'string' },
description: 'List of AWS services to include'
},
regions: {
type: 'array',
items: { type: 'string' },
description: 'List of AWS regions to include'
},
startDate: {
type: 'string',
description: 'Start date for analysis (ISO 8601 format)'
},
endDate: {
type: 'string',
description: 'End date for analysis (ISO 8601 format)'
},
minCost: {
type: 'number',
description: 'Minimum cost threshold'
},
maxCost: {
type: 'number',
description: 'Maximum cost threshold'
},
tags: {
type: 'object',
description: 'Key-value pairs for tag filtering'
}
}
}
},
handler: this.handleCostAnalysis.bind(this)
};
// Usage Comparison Tool
this.toolRegistry['compare_usage'] = {
schema: {
name: 'compare_usage',
description: 'Compare AWS usage and costs between two time periods',
inputSchema: {
type: 'object',
properties: {
currentPeriod: {
type: 'object',
properties: {
startDate: { type: 'string', description: 'Current period start date' },
endDate: { type: 'string', description: 'Current period end date' }
},
required: ['startDate', 'endDate']
},
previousPeriod: {
type: 'object',
properties: {
startDate: { type: 'string', description: 'Previous period start date' },
endDate: { type: 'string', description: 'Previous period end date' }
},
required: ['startDate', 'endDate']
},
accountId: { type: 'string', description: 'AWS account ID to filter by' },
services: {
type: 'array',
items: { type: 'string' },
description: 'List of AWS services to include'
},
regions: {
type: 'array',
items: { type: 'string' },
description: 'List of AWS regions to include'
}
},
required: ['currentPeriod', 'previousPeriod']
}
},
handler: this.handleUsageComparison.bind(this)
};
// Trend Analysis Tool
this.toolRegistry['analyze_trends'] = {
schema: {
name: 'analyze_trends',
description: 'Analyze cost trends and patterns over time',
inputSchema: {
type: 'object',
properties: {
accountId: { type: 'string', description: 'AWS account ID to filter by' },
service: { type: 'string', description: 'Specific AWS service to analyze' },
startDate: { type: 'string', description: 'Analysis start date (ISO 8601 format)' },
endDate: { type: 'string', description: 'Analysis end date (ISO 8601 format)' },
regions: {
type: 'array',
items: { type: 'string' },
description: 'List of AWS regions to include'
}
},
required: ['startDate', 'endDate']
}
},
handler: this.handleTrendAnalysis.bind(this)
};
// Anomaly Detection Tool
this.toolRegistry['detect_anomalies'] = {
schema: {
name: 'detect_anomalies',
description: 'Detect cost anomalies and unusual spending patterns',
inputSchema: {
type: 'object',
properties: {
accountId: { type: 'string', description: 'AWS account ID to filter by' },
startDate: { type: 'string', description: 'Analysis start date (ISO 8601 format)' },
endDate: { type: 'string', description: 'Analysis end date (ISO 8601 format)' },
thresholdMultiplier: {
type: 'number',
minimum: 1,
maximum: 5,
default: 2.0,
description: 'Sensitivity threshold for anomaly detection (1-5)'
},
services: {
type: 'array',
items: { type: 'string' },
description: 'List of AWS services to include'
},
regions: {
type: 'array',
items: { type: 'string' },
description: 'List of AWS regions to include'
}
},
required: ['startDate', 'endDate']
}
},
handler: this.handleAnomalyDetection.bind(this)
};
// Cost Ranking Tool
this.toolRegistry['rank_cost_drivers'] = {
schema: {
name: 'rank_cost_drivers',
description: 'Rank and identify top cost drivers and expensive resources',
inputSchema: {
type: 'object',
properties: {
accountId: { type: 'string', description: 'AWS account ID to filter by' },
startDate: { type: 'string', description: 'Analysis start date (ISO 8601 format)' },
endDate: { type: 'string', description: 'Analysis end date (ISO 8601 format)' },
services: {
type: 'array',
items: { type: 'string' },
description: 'List of AWS services to include'
},
regions: {
type: 'array',
items: { type: 'string' },
description: 'List of AWS regions to include'
},
limit: {
type: 'number',
minimum: 1,
maximum: 50,
default: 10,
description: 'Maximum number of results to return'
}
},
required: ['startDate', 'endDate']
}
},
handler: this.handleCostRanking.bind(this)
};
// Authentication Tools (if auth enabled)
if (this.authEnabled) {
this.toolRegistry['get_auth_url'] = {
schema: {
name: 'get_auth_url',
description: 'Get Google OAuth2 authentication URL for user login',
inputSchema: {
type: 'object',
properties: {
scopes: {
type: 'array',
items: { type: 'string' },
description: 'OAuth2 scopes to request',
default: ['openid', 'email', 'profile']
}
}
}
},
handler: this.handleGetAuthUrl.bind(this)
};
this.toolRegistry['authenticate'] = {
schema: {
name: 'authenticate',
description: 'Complete authentication using OAuth2 authorization code',
inputSchema: {
type: 'object',
properties: {
code: {
type: 'string',
description: 'OAuth2 authorization code from callback'
}
},
required: ['code']
}
},
handler: this.handleAuthenticate.bind(this)
};
this.toolRegistry['validate_session'] = {
schema: {
name: 'validate_session',
description: 'Validate current authentication session',
inputSchema: {
type: 'object',
properties: {}
}
},
handler: this.handleValidateSession.bind(this)
};
this.toolRegistry['logout'] = {
schema: {
name: 'logout',
description: 'Logout and invalidate current session',
inputSchema: {
type: 'object',
properties: {}
}
},
handler: this.handleLogout.bind(this)
};
}
this.logger.info('Tool registry initialized', {
toolCount: Object.keys(this.toolRegistry).length,
tools: Object.keys(this.toolRegistry),
authEnabled: this.authEnabled
});
}
private checkRateLimit(clientId: string = 'default'): boolean {
const now = Date.now();
const windowMs = 60000; // 1 minute window
const maxRequests = 100; // Max requests per minute
const entry = this.rateLimitMap.get(clientId);
if (!entry || now > entry.resetTime) {
this.rateLimitMap.set(clientId, {
count: 1,
resetTime: now + windowMs
});
return true;
}
if (entry.count >= maxRequests) {
return false;
}
entry.count++;
return true;
}
private validateResourceLimits(): void {
if (this.activeRequests >= this.resourceLimits.maxConcurrentRequests) {
throw new McpError(
ErrorCode.InternalError,
'Server is at maximum capacity. Please try again later.'
);
}
}
private async authenticateRequest(headers?: Record<string, string>): Promise<AuthSession | null> {
if (!this.authEnabled) {
return null; // Skip authentication if disabled
}
const authHeader = headers?.['authorization'];
if (!authHeader) {
this.logger.logSecurityEvent('missing_auth_header', {});
throw new McpError(
ErrorCode.InvalidRequest,
'Authentication required. Please provide Authorization header with Bearer token.'
);
}
const token = authHeader.replace('Bearer ', '');
if (!token) {
this.logger.logSecurityEvent('invalid_auth_header', {});
throw new McpError(
ErrorCode.InvalidRequest,
'Invalid authorization header format. Use: Authorization: Bearer <token>'
);
}
// Verify JWT token
const jwtPayload = this.authManager.verifyJWT(token);
if (!jwtPayload) {
this.logger.logSecurityEvent('invalid_jwt_token', {});
throw new McpError(
ErrorCode.InvalidRequest,
'Invalid or expired authentication token.'
);
}
// Validate session
const session = await this.authManager.validateSession(jwtPayload.sessionId);
if (!session) {
this.logger.logSecurityEvent('invalid_session', { sessionId: jwtPayload.sessionId });
throw new McpError(
ErrorCode.InvalidRequest,
'Session expired or invalid. Please re-authenticate.'
);
}
this.logger.info('Request authenticated', {
userId: session.userId,
email: session.email,
permissions: session.permissions
});
return session;
}
private async authorizeToolAccess(session: AuthSession | null, toolName: string): Promise<void> {
if (!this.authEnabled || !session) {
return; // Skip authorization if auth disabled or no session
}
// Define required permissions for each tool
const toolPermissions: Record<string, string[]> = {
'analyze_costs': ['billing:read'],
'compare_usage': ['billing:read'],
'analyze_trends': ['billing:read'],
'detect_anomalies': ['billing:read'],
'rank_cost_drivers': ['billing:read']
};
const requiredPermissions = toolPermissions[toolName] || ['billing:read'];
const hasPermission = await this.authManager.hasAnyPermission(
session.id,
requiredPermissions
);
if (!hasPermission) {
this.logger.logSecurityEvent('unauthorized_tool_access', {
userId: session.userId,
toolName,
requiredPermissions,
userPermissions: session.permissions
});
throw new McpError(
ErrorCode.InvalidRequest,
`Access denied. Required permissions: ${requiredPermissions.join(', ')}`
);
}
this.logger.info('Tool access authorized', {
userId: session.userId,
toolName,
permissions: session.permissions
});
}
private sanitizeErrorMessage(error: any): string {
if (error instanceof Error) {
// Remove sensitive information from error messages
let message = error.message;
// Remove AWS credentials or keys
message = message.replace(/AKIA[0-9A-Z]{16}/g, '[REDACTED_ACCESS_KEY]');
message = message.replace(/[A-Za-z0-9/+=]{40}/g, '[REDACTED_SECRET]');
// Remove file paths that might contain sensitive info
message = message.replace(/\/[^\s]+/g, '[REDACTED_PATH]');
return message;
}
return 'An unknown error occurred';
}
private async executeWithTimeout<T>(
operation: () => Promise<T>,
timeoutMs: number = this.resourceLimits.requestTimeoutMs
): Promise<T> {
return Promise.race([
operation(),
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error('Operation timed out')), timeoutMs)
)
]);
}
private setupHandlers(): void {
// List tools handler
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools = Object.values(this.toolRegistry).map(tool => tool.schema);
this.logger.info('Listed available tools', { toolCount: tools.length });
return { tools };
});
// Call tool handler with comprehensive error handling and authentication
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const startTime = Date.now();
let session: AuthSession | null = null;
try {
// Authentication and authorization
session = await this.authenticateRequest(request.params._meta?.headers);
await this.authorizeToolAccess(session, name);
// Rate limiting
if (!this.checkRateLimit(session?.userId)) {
throw new McpError(
ErrorCode.InternalError,
'Rate limit exceeded. Please try again later.'
);
}
// Resource management
this.validateResourceLimits();
this.activeRequests++;
this.logger.info('Tool call received', {
toolName: name,
arguments: args,
activeRequests: this.activeRequests,
userId: session?.userId,
userEmail: session?.email
});
// Validate tool exists
if (!this.toolRegistry[name]) {
const error = new McpError(
ErrorCode.MethodNotFound,
`Tool '${name}' not found`
);
this.logger.error('Tool not found', {
toolName: name,
userId: session?.userId
});
throw error;
}
// Parameter validation - always validate if args provided
this.validateParameters(name, args || {});
// Execute with timeout
const result = await this.executeWithTimeout(
() => this.toolRegistry[name].handler(args || {})
);
const duration = Date.now() - startTime;
this.logger.info('Tool call completed successfully', {
toolName: name,
duration,
resultType: typeof result.content,
activeRequests: this.activeRequests - 1,
userId: session?.userId
});
return result;
} catch (error) {
const duration = Date.now() - startTime;
const sanitizedError = this.sanitizeErrorMessage(error);
this.logger.error('Tool call failed', {
toolName: name,
duration,
error: sanitizedError,
activeRequests: this.activeRequests - 1,
userId: session?.userId
});
if (error instanceof McpError) {
throw error;
}
// Handle specific error types
if (error instanceof Error) {
if (error.message.includes('timeout')) {
throw new McpError(
ErrorCode.InternalError,
'Request timed out. Please try again with a smaller date range.'
);
}
if (error.message.includes('Rate limit')) {
throw new McpError(
ErrorCode.InternalError,
'AWS API rate limit exceeded. Please try again later.'
);
}
if (error.message.includes('credentials')) {
throw new McpError(
ErrorCode.InternalError,
'Authentication failed. Please check your AWS credentials.'
);
}
}
throw new McpError(
ErrorCode.InternalError,
`Tool execution failed: ${sanitizedError}`
);
} finally {
this.activeRequests = Math.max(0, this.activeRequests - 1);
}
});
this.logger.info('MCP Server handlers initialized');
}
private validateParameters(toolName: string, args: any): void {
// Common parameter validation
if (args.startDate && args.endDate) {
const startDate = new Date(args.startDate);
const endDate = new Date(args.endDate);
if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
throw new McpError(
ErrorCode.InvalidParams,
'Invalid date format. Please use ISO 8601 format (YYYY-MM-DDTHH:mm:ss.sssZ)'
);
}
if (startDate >= endDate) {
throw new McpError(
ErrorCode.InvalidParams,
'Start date must be before end date'
);
}
// Limit date range to prevent excessive data queries
const maxRangeMs = 365 * 24 * 60 * 60 * 1000; // 1 year
if (endDate.getTime() - startDate.getTime() > maxRangeMs) {
throw new McpError(
ErrorCode.InvalidParams,
'Date range cannot exceed 1 year'
);
}
}
// Tool-specific validation
switch (toolName) {
case 'detect_anomalies':
if (args.thresholdMultiplier && (args.thresholdMultiplier < 1 || args.thresholdMultiplier > 5)) {
throw new McpError(
ErrorCode.InvalidParams,
'Threshold multiplier must be between 1 and 5'
);
}
break;
case 'rank_cost_drivers':
if (args.limit && (args.limit < 1 || args.limit > 50)) {
throw new McpError(
ErrorCode.InvalidParams,
'Limit must be between 1 and 50'
);
}
break;
}
}
private async handleCostAnalysis(args: any): Promise<CallToolResult> {
// Validate parameters first
if (args.startDate) {
const startDate = new Date(args.startDate);
if (isNaN(startDate.getTime())) {
throw new McpError(ErrorCode.InvalidParams, 'Invalid startDate format');
}
}
if (args.endDate) {
const endDate = new Date(args.endDate);
if (isNaN(endDate.getTime())) {
throw new McpError(ErrorCode.InvalidParams, 'Invalid endDate format');
}
}
const validatedArgs = CostAnalysisSchema.parse(args);
// Convert string dates to Date objects
const filters: any = { ...validatedArgs };
if (validatedArgs.startDate) {
filters.startDate = new Date(validatedArgs.startDate);
}
if (validatedArgs.endDate) {
filters.endDate = new Date(validatedArgs.endDate);
}
// Get billing records
const allRecords = await this.billingClient.getBillingData();
const filteredRecords = this.billingAnalyzer.filterBillingRecords(allRecords, filters);
if (filteredRecords.length === 0) {
return {
content: [{
type: 'text',
text: JSON.stringify({
message: 'No billing data found matching the specified criteria',
filters: validatedArgs,
recordCount: 0
}, null, 2)
}],
isError: false
};
}
const analysisResult = this.billingAnalyzer.analyzeCosts(filteredRecords);
return {
content: [{
type: 'text',
text: JSON.stringify({
analysis: analysisResult,
metadata: {
recordCount: filteredRecords.length,
filters: validatedArgs,
generatedAt: new Date().toISOString()
}
}, null, 2)
}]
};
}
private async handleUsageComparison(args: any): Promise<CallToolResult> {
const validatedArgs = UsageComparisonSchema.parse(args);
const allRecords = await this.billingClient.getBillingData();
// Filter records for current period
const currentFilters = {
accountId: validatedArgs.accountId,
services: validatedArgs.services,
regions: validatedArgs.regions,
startDate: new Date(validatedArgs.currentPeriod.startDate),
endDate: new Date(validatedArgs.currentPeriod.endDate)
};
const currentRecords = this.billingAnalyzer.filterBillingRecords(allRecords, currentFilters);
// Filter records for previous period
const previousFilters = {
accountId: validatedArgs.accountId,
services: validatedArgs.services,
regions: validatedArgs.regions,
startDate: new Date(validatedArgs.previousPeriod.startDate),
endDate: new Date(validatedArgs.previousPeriod.endDate)
};
const previousRecords = this.billingAnalyzer.filterBillingRecords(allRecords, previousFilters);
if (currentRecords.length === 0 && previousRecords.length === 0) {
return {
content: [{
type: 'text',
text: JSON.stringify({
message: 'No billing data found for either period',
filters: validatedArgs,
currentRecordCount: 0,
previousRecordCount: 0
}, null, 2)
}],
isError: false
};
}
const comparisonResult = this.billingAnalyzer.compareUsage(currentRecords, previousRecords);
return {
content: [{
type: 'text',
text: JSON.stringify({
comparison: comparisonResult,
metadata: {
currentRecordCount: currentRecords.length,
previousRecordCount: previousRecords.length,
filters: validatedArgs,
generatedAt: new Date().toISOString()
}
}, null, 2)
}]
};
}
private async handleTrendAnalysis(args: any): Promise<CallToolResult> {
const validatedArgs = TrendAnalysisSchema.parse(args);
const filters = {
accountId: validatedArgs.accountId,
regions: validatedArgs.regions,
startDate: new Date(validatedArgs.startDate),
endDate: new Date(validatedArgs.endDate)
};
const allRecords = await this.billingClient.getBillingData();
const filteredRecords = this.billingAnalyzer.filterBillingRecords(allRecords, filters);
if (filteredRecords.length === 0) {
return {
content: [{
type: 'text',
text: JSON.stringify({
message: 'No billing data found matching the specified criteria',
filters: validatedArgs,
recordCount: 0
}, null, 2)
}],
isError: false
};
}
const trendsResult = this.billingAnalyzer.analyzeTrends(filteredRecords, validatedArgs.service);
return {
content: [{
type: 'text',
text: JSON.stringify({
trends: trendsResult,
metadata: {
recordCount: filteredRecords.length,
filters: validatedArgs,
generatedAt: new Date().toISOString()
}
}, null, 2)
}]
};
}
private async handleAnomalyDetection(args: any): Promise<CallToolResult> {
const validatedArgs = AnomalyDetectionSchema.parse(args);
const filters = {
accountId: validatedArgs.accountId,
services: validatedArgs.services,
regions: validatedArgs.regions,
startDate: new Date(validatedArgs.startDate),
endDate: new Date(validatedArgs.endDate)
};
const allRecords = await this.billingClient.getBillingData();
const filteredRecords = this.billingAnalyzer.filterBillingRecords(allRecords, filters);
if (filteredRecords.length < 7) {
return {
content: [{
type: 'text',
text: JSON.stringify({
message: 'Insufficient data for anomaly detection (minimum 7 data points required)',
filters: validatedArgs,
recordCount: filteredRecords.length
}, null, 2)
}],
isError: false
};
}
const anomaliesResult = this.billingAnalyzer.detectAnomalies(
filteredRecords,
validatedArgs.thresholdMultiplier
);
return {
content: [{
type: 'text',
text: JSON.stringify({
anomalies: anomaliesResult,
metadata: {
recordCount: filteredRecords.length,
filters: validatedArgs,
generatedAt: new Date().toISOString()
}
}, null, 2)
}]
};
}
private async handleCostRanking(args: any): Promise<CallToolResult> {
const validatedArgs = CostRankingSchema.parse(args);
const filters = {
accountId: validatedArgs.accountId,
services: validatedArgs.services,
regions: validatedArgs.regions,
startDate: new Date(validatedArgs.startDate),
endDate: new Date(validatedArgs.endDate)
};
const allRecords = await this.billingClient.getBillingData();
const filteredRecords = this.billingAnalyzer.filterBillingRecords(allRecords, filters);
if (filteredRecords.length === 0) {
return {
content: [{
type: 'text',
text: JSON.stringify({
message: 'No billing data found matching the specified criteria',
filters: validatedArgs,
recordCount: 0
}, null, 2)
}],
isError: false
};
}
const rankingResult = this.billingAnalyzer.rankCostDrivers(filteredRecords);
// Apply limit to results
const limitedRanking = {
...rankingResult,
topServices: rankingResult.topServices.slice(0, validatedArgs.limit),
topRegions: rankingResult.topRegions.slice(0, validatedArgs.limit),
costDrivers: rankingResult.costDrivers.slice(0, validatedArgs.limit)
};
return {
content: [{
type: 'text',
text: JSON.stringify({
ranking: limitedRanking,
metadata: {
recordCount: filteredRecords.length,
filters: validatedArgs,
generatedAt: new Date().toISOString()
}
}, null, 2)
}]
};
}
public async start(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
this.logger.info('MCP Server connected via stdio transport');
}
private async handleGetAuthUrl(args: any): Promise<CallToolResult> {
const scopes = args.scopes || ['openid', 'email', 'profile'];
const authUrl = this.authManager.getAuthUrl(scopes);
return {
content: [{
type: 'text',
text: JSON.stringify({
authUrl,
instructions: 'Visit this URL to authenticate with Google OAuth2',
scopes,
generatedAt: new Date().toISOString()
}, null, 2)
}]
};
}
private async handleAuthenticate(args: any): Promise<CallToolResult> {
const { code } = args;
try {
const session = await this.authManager.handleCallback(code);
const jwt = this.authManager.generateJWT(session);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
message: 'Authentication successful',
session: {
userId: session.userId,
email: session.email,
permissions: session.permissions,
expiresAt: session.expiresAt.toISOString()
},
token: jwt,
generatedAt: new Date().toISOString()
}, null, 2)
}],
isError: false
};
} catch (error: any) {
const sanitizedError = this.sanitizeErrorMessage(error);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
error: 'Authentication failed',
message: sanitizedError,
generatedAt: new Date().toISOString()
}, null, 2)
}],
isError: false
};
}
}
private async handleValidateSession(_args: any, headers?: Record<string, string>): Promise<CallToolResult> {
try {
const session = await this.authenticateRequest(headers);
if (!session) {
return {
content: [{
type: 'text',
text: JSON.stringify({
valid: false,
message: 'No active session',
generatedAt: new Date().toISOString()
}, null, 2)
}],
isError: false
};
}
return {
content: [{
type: 'text',
text: JSON.stringify({
valid: true,
session: {
userId: session.userId,
email: session.email,
permissions: session.permissions,
expiresAt: session.expiresAt.toISOString()
},
generatedAt: new Date().toISOString()
}, null, 2)
}],
isError: false
};
} catch (error: any) {
return {
content: [{
type: 'text',
text: JSON.stringify({
valid: false,
error: 'Session validation failed',
message: this.sanitizeErrorMessage(error),
generatedAt: new Date().toISOString()
}, null, 2)
}],
isError: false
};
}
}
private async handleLogout(_args: any, headers?: Record<string, string>): Promise<CallToolResult> {
try {
const session = await this.authenticateRequest(headers);
if (session) {
await this.authManager.deleteSession(session.id);
}
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
message: 'Logout successful',
generatedAt: new Date().toISOString()
}, null, 2)
}],
isError: false
};
} catch (error: any) {
return {
content: [{
type: 'text',
text: JSON.stringify({
success: false,
error: 'Logout failed',
message: this.sanitizeErrorMessage(error),
generatedAt: new Date().toISOString()
}, null, 2)
}],
isError: false
};
}
}
public getToolRegistry(): ToolRegistry {
return this.toolRegistry;
}
public getAuthManager(): AuthManager {
return this.authManager;
}
public isAuthEnabled(): boolean {
return this.authEnabled;
}
}