Skip to main content
Glama

MCP TypeScript Template

jwtStrategy.ts6 kB
/** * @fileoverview Implements the JWT authentication strategy. * This module provides a concrete implementation of the AuthStrategy for validating * JSON Web Tokens (JWTs). It encapsulates all logic related to JWT verification, * including secret key management and payload validation. * @module src/mcp-server/transports/auth/strategies/JwtStrategy */ import { jwtVerify } from 'jose'; import { injectable, inject } from 'tsyringe'; import { config as ConfigType } from '@/config/index.js'; import { AppConfig, Logger } from '@/container/tokens.js'; import { JsonRpcErrorCode, McpError } from '@/types-global/errors.js'; import { ErrorHandler, logger as LoggerType, requestContextService, } from '@/utils/index.js'; import type { AuthInfo } from '@/mcp-server/transports/auth/lib/authTypes.js'; import type { AuthStrategy } from '@/mcp-server/transports/auth/strategies/authStrategy.js'; @injectable() export class JwtStrategy implements AuthStrategy { private readonly secretKey: Uint8Array | null; private readonly env: string; private readonly devMcpClientId: string; private readonly devMcpScopes: string[]; constructor( @inject(AppConfig) private config: typeof ConfigType, @inject(Logger) private logger: typeof LoggerType, ) { const context = requestContextService.createRequestContext({ operation: 'JwtStrategy.constructor', }); this.logger.debug('Initializing JwtStrategy...', context); this.env = this.config.environment; this.devMcpClientId = this.config.devMcpClientId || 'dev-client-id'; this.devMcpScopes = this.config.devMcpScopes || ['dev-scope']; const secretKey = this.config.mcpAuthSecretKey; if (this.env === 'production' && !secretKey) { this.logger.fatal( 'CRITICAL: MCP_AUTH_SECRET_KEY is not set in production for JWT auth.', context, ); throw new McpError( JsonRpcErrorCode.ConfigurationError, 'MCP_AUTH_SECRET_KEY must be set for JWT auth in production.', context, ); } else if (!secretKey) { this.logger.warning( 'MCP_AUTH_SECRET_KEY is not set. JWT auth will be bypassed (DEV ONLY).', context, ); this.secretKey = null; } else { this.logger.info('JWT secret key loaded successfully.', context); this.secretKey = new TextEncoder().encode(secretKey); } } async verify(token: string): Promise<AuthInfo> { const context = requestContextService.createRequestContext({ operation: 'JwtStrategy.verify', }); this.logger.debug('Attempting to verify JWT.', context); // Handle development mode bypass if (!this.secretKey) { if (this.env !== 'production') { this.logger.warning( 'Bypassing JWT verification: No secret key (DEV ONLY).', context, ); return { token: 'dev-mode-placeholder-token', clientId: this.devMcpClientId, scopes: this.devMcpScopes, }; } // This path is defensive. The constructor should prevent this state in production. this.logger.crit('Auth secret key is missing in production.', context); throw new McpError( JsonRpcErrorCode.ConfigurationError, 'Auth secret key is missing in production. This indicates a server configuration error.', context, ); } try { const { payload: decoded } = await jwtVerify(token, this.secretKey); this.logger.debug('JWT signature verified successfully.', { ...context, claims: decoded, }); const clientId = typeof decoded.cid === 'string' ? decoded.cid : typeof decoded.client_id === 'string' ? decoded.client_id : undefined; if (!clientId) { this.logger.warning( "Invalid token: missing 'cid' or 'client_id' claim.", context, ); throw new McpError( JsonRpcErrorCode.Unauthorized, "Invalid token: missing 'cid' or 'client_id' claim.", context, ); } let scopes: string[] = []; if ( Array.isArray(decoded.scp) && decoded.scp.every((s) => typeof s === 'string') ) { scopes = decoded.scp; } else if (typeof decoded.scope === 'string' && decoded.scope.trim()) { scopes = decoded.scope.split(' ').filter(Boolean); } if (scopes.length === 0) { this.logger.warning( "Invalid token: missing or empty 'scp' or 'scope' claim.", context, ); throw new McpError( JsonRpcErrorCode.Unauthorized, 'Token must contain valid, non-empty scopes.', context, ); } const tenantId = typeof decoded.tid === 'string' ? decoded.tid : undefined; const authInfo: AuthInfo = { token, clientId, scopes, ...(decoded.sub && { subject: decoded.sub }), ...(tenantId && { tenantId }), }; this.logger.info('JWT verification successful.', { ...context, clientId, scopes, ...(tenantId ? { tenantId } : {}), }); return authInfo; } catch (error) { // If the error is already a structured McpError, re-throw it directly. if (error instanceof McpError) { throw error; } const message = error instanceof Error && error.name === 'JWTExpired' ? 'Token has expired.' : 'Token verification failed.'; this.logger.warning(`JWT verification failed: ${message}`, { ...context, errorName: error instanceof Error ? error.name : 'Unknown', }); throw ErrorHandler.handleError(error, { operation: 'JwtStrategy.verify', context, rethrow: true, errorCode: JsonRpcErrorCode.Unauthorized, errorMapper: () => new McpError(JsonRpcErrorCode.Unauthorized, message, context), }); } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mintedmaterial/mcp-ts-template'

If you have feedback or need assistance with the MCP directory API, please join our Discord server